commit e8ca5ddb7fca2eb82c8a7baeb865ca75c69f3c94 Author: Jeffrey Griffin Date: Thu Dec 19 11:05:19 2019 -0800 Big Bang diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..dd4d471 --- /dev/null +++ b/README.md @@ -0,0 +1,147 @@ +# Secure Value Recovery Service (Beta) + +## Building the SGX enclave (optional) + +### Building reproducibly with Docker + +#### Prerequisites: +- GNU Make +- Docker (able to run debian image) + +````` +$ make -C /enclave +````` + +The default docker-install target will create a reproducible build environment image using +`enclave/docker/Dockerfile`, build the enclave inside a container based on the image, and +install the resulting enclave into `service/kbupd/res/enclave/`. The Dockerfile will +download a stock dated-snapshot debian Docker image. The Debian project builds their +docker images reproducibly, based on the a snapshot of the debian repos on the date of the +build from the [Debian Snapshot Project](https://snapshot.debian.org/). Make will then be +run inside the newly built Docker Debian image as in the [Building with +Debian](#building-with-debian) section below: + +NB: the installed enclave will be signed with the SGX debug flag enabled by an +automatically generated signing key. Due to Intel SGX licensing requirements, a debug +enclave can currently only be run with SGX debugging enabled, allowing inspection of its +encrypted memory, and invalidating its security properties. To use an enclave in +production, provide the Intel-whitelisted signing key as +`enclave/libkbupd_enclave.hardened.key` before building. Alternatively, the generated +`enclave/build/libkbupd_enclave.hardened.signdata` file can be signed and saved as +`enclave/build/libkbupd_enclave.sig` with corresponding public key at +`enclave/libkbupd_enclave.pub`, and signed using `make sign install`. + +### Building with Debian + +#### Prerequisites: +- GNU Make +- cmake +- ninja-build +- gcc +- ocaml-native-compilers +- ocamlbuild +- automake/autoconf/libtool/pkg-config +- libssl-dev +- libcurl4-openssl-dev +- protobuf-compiler +- libprotobuf-dev +- llvm-dev +- libclang-dev +- clang +- git +- devscripts/debhelper/fakeroot +- rust 1.37.0 toolchain from rustup +- [Intel SGX SDK v2.7.1 SDK](https://github.com/intel/linux-sgx/tree/sgx_2.7.1) build dependencies + +````` +$ make -C /enclave debuild install +````` + +`debuild` is a debian tool used to build debian packages after it sanitizes the +environment and installs build dependences. The primary advantage of using debian +packaging tools in this case is to leverage the [Reproducible +Builds](https://wiki.debian.org/ReproducibleBuilds) project. While building a debian +package, `debuild` will record the names and versions of all detected build dependencies +into a *.buildinfo file, for future reproducibility debugging. + +The `debuild` target also builds parts needed from the Intel SGX SDK v2.7.1 after cloning it +from github. + +The `install` target copies the enclave to `service/kbupd/res/enclave/`, which should +potentially be checked in to be used with the service. + +The `sign` target may also be used as described in [Building reproducibly with +Docker](#building-reproducibly-with-docker) to produce a release-mode enclave. + +### Building without Docker or Debian: + +#### Prerequisites: +- GNU Make +- cmake +- ninja-build +- gcc +- ocaml-native-compilers +- ocamlbuild +- automake/autoconf/libtool/pkg-config +- libssl-dev +- libcurl4-openssl-dev +- protobuf-compiler +- libprotobuf-dev +- llvm-dev +- libclang-dev +- clang +- git +- rust 1.37.0 toolchain from rustup +- [Intel SGX SDK v2.7.1 SDK](https://github.com/intel/linux-sgx/tree/sgx_2.7.1) build dependencies + +````` +$ make -C /enclave all install +````` + +The `all` target will probably fail to reproduce the same binary as above, but doesn't +require Docker or Debian Linux. + +The `sign` target may also be used as described in [Building reproducibly with +Docker](#building-reproducibly-with-docker) to produce a release-mode enclave. + +## Building the service + +### Building with Docker + +#### Prerequisites: +- GNU Make +- Docker (able to run ubuntu image) + +````` +$ make -C /service docker +````` + +### Building without Docker + +#### Prerequisites: +- GNU Make +- a C compiler +- rust toolchain (i.e. rustc, cargo) +- libsgx-enclave-common [from source](https://github.com/intel/linux-sgx/tree/master#install-the-intelr-sgx-psw) or [prebuilt](https://download.01.org/intel-sgx/sgx_repo/ubuntu/pool/main/libs/libsgx-enclave-common/) +- libssl-dev (OpenSSL) +- libseccomp-dev +- pkg-config +- protobuf-compiler +- [Intel SGX SDK SDK](https://github.com/intel/linux-sgx) headers (common/inc/sgx*.h) installed in a system include directory + +````` +$ make -C /service all +````` + +## Running the service + +### Runtime requirements: +- libsgx-enclave-common >= 2.7.101 [from source](https://github.com/intel/linux-sgx/tree/master#install-the-intelr-sgx-psw) or [prebuilt](https://download.01.org/intel-sgx/sgx_repo/ubuntu/pool/main/libs/libsgx-enclave-common/) +- linux-sgx-driver >= 2.6.0 [from source](https://github.com/intel/linux-sgx-driver) or [prebuilt](https://download.01.org/intel-sgx/sgx-linux/2.7.1/distro/ubuntu18.04-server/) +- libssl1.1 (OpenSSL) +- libseccomp2 +- libprotobuf10 + +````` +$ service/build/target/release/kbupd help +````` diff --git a/enclave/.gitignore b/enclave/.gitignore new file mode 100644 index 0000000..b8f9f9c --- /dev/null +++ b/enclave/.gitignore @@ -0,0 +1,10 @@ +/*.key +/*.pub +/build +/target +/debian/.debhelper +/debian/debhelper-build-stamp +/debian/kbupd-enclave/ +/debian/kbupd-enclave.substvars +/debian/*.deb +/debian/files \ No newline at end of file diff --git a/enclave/Cargo.lock b/enclave/Cargo.lock new file mode 100644 index 0000000..b5ca855 --- /dev/null +++ b/enclave/Cargo.lock @@ -0,0 +1,819 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "arrayref" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "autocfg" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "backtrace" +version = "0.3.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "base64" +version = "0.10.1" +source = "git+https://github.com/marshallpierce/rust-base64.git?rev=07b1d6b713cc2bd7d107185bd0d14bf06cddfb48#07b1d6b713cc2bd7d107185bd0d14bf06cddfb48" + +[[package]] +name = "bumpalo" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytes" +version = "0.5.0" +source = "git+https://github.com/tokio-rs/bytes.git?rev=ebe96021b0eaf52be1fedd0a925f4384275c9cc4#ebe96021b0eaf52be1fedd0a925f4384275c9cc4" + +[[package]] +name = "c2-chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chrono" +version = "0.4.9" +source = "git+https://github.com/geogriff-signal/chrono.git?rev=de22e82a1b00b8f015a1d736e497c3177e8e8c9f#de22e82a1b00b8f015a1d736e497c3177e8e8c9f" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "failure" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hashbrown" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "indoc" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "indoc-impl 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "indoc-impl" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "unindent 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "intrusive-collections" +version = "0.8.1" +source = "git+https://github.com/geogriff-signal/intrusive-rs.git?rev=3c14ea97598616a2beaa97cd5fbbb27ba574fe77#3c14ea97598616a2beaa97cd5fbbb27ba574fe77" +dependencies = [ + "memoffset 0.5.1 (git+https://github.com/Gilnaa/memoffset.git?rev=c14a1633760b55999a687bbdf17cb1fa3d1b2f9a)", +] + +[[package]] +name = "itertools" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "itertools" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "js-sys" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "wasm-bindgen 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kbupd_enclave" +version = "0.1.0" +dependencies = [ + "base64 0.10.1 (git+https://github.com/marshallpierce/rust-base64.git?rev=07b1d6b713cc2bd7d107185bd0d14bf06cddfb48)", + "bytes 0.5.0 (git+https://github.com/tokio-rs/bytes.git?rev=ebe96021b0eaf52be1fedd0a925f4384275c9cc4)", + "chrono 0.4.9 (git+https://github.com/geogriff-signal/chrono.git?rev=de22e82a1b00b8f015a1d736e497c3177e8e8c9f)", + "hashbrown 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "intrusive-collections 0.8.1 (git+https://github.com/geogriff-signal/intrusive-rs.git?rev=3c14ea97598616a2beaa97cd5fbbb27ba574fe77)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "mockers 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mockers_derive 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "no-std-compat 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.5.0 (git+https://github.com/geogriff-signal/prost.git?rev=907f7d6e714d0b449d75269d532561c71d3ed250)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (git+https://github.com/geogriff-signal/serde_json.git?rev=d79b0c67f62e168d4872bb8694377ffd97b8949f)", + "sgx_ffi 0.1.0", + "sgxsd_ffi 0.1.0", + "snow 0.6.1 (git+https://github.com/geogriff-signal/snow.git?rev=d8d00a37c8e39b2557d23a26cc4f722595b4f2d9)", + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "test_ffi 0.1.0", + "webpki 0.21.0 (git+https://github.com/geogriff-signal/webpki.git?rev=32ab63c46edfbfe9c1fe68008ba8e247eb387ca3)", +] + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memoffset" +version = "0.5.1" +source = "git+https://github.com/Gilnaa/memoffset.git?rev=c14a1633760b55999a687bbdf17cb1fa3d1b2f9a#c14a1633760b55999a687bbdf17cb1fa3d1b2f9a" +dependencies = [ + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mockers" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "select-rustc 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mockers_derive" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "indoc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-quote 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "no-std-compat" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro-hack" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-quote" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-quote-impl 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-quote-impl" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "prost" +version = "0.5.0" +source = "git+https://github.com/geogriff-signal/prost.git?rev=907f7d6e714d0b449d75269d532561c71d3ed250#907f7d6e714d0b449d75269d532561c71d3ed250" +dependencies = [ + "bytes 0.5.0 (git+https://github.com/tokio-rs/bytes.git?rev=ebe96021b0eaf52be1fedd0a925f4384275c9cc4)", + "prost-derive 0.5.0 (git+https://github.com/geogriff-signal/prost.git?rev=907f7d6e714d0b449d75269d532561c71d3ed250)", +] + +[[package]] +name = "prost-derive" +version = "0.5.0" +source = "git+https://github.com/geogriff-signal/prost.git?rev=907f7d6e714d0b449d75269d532561c71d3ed250#907f7d6e714d0b449d75269d532561c71d3ed250" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ring" +version = "0.16.9" +source = "git+https://github.com/geogriff-signal/ring.git?rev=d0b3a4dcf1f24a2700280f9f7541cb5079398ec6#d0b3a4dcf1f24a2700280f9f7541cb5079398ec6" +dependencies = [ + "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.27 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "select-rustc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.40" +source = "git+https://github.com/geogriff-signal/serde_json.git?rev=d79b0c67f62e168d4872bb8694377ffd97b8949f#d79b0c67f62e168d4872bb8694377ffd97b8949f" +dependencies = [ + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sgx_ffi" +version = "0.1.0" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "mockers 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mockers_derive 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "test_ffi 0.1.0", +] + +[[package]] +name = "sgxsd_ffi" +version = "0.1.0" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "mockers 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mockers_derive 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sgx_ffi 0.1.0", + "test_ffi 0.1.0", +] + +[[package]] +name = "snow" +version = "0.6.1" +source = "git+https://github.com/geogriff-signal/snow.git?rev=d8d00a37c8e39b2557d23a26cc4f722595b4f2d9#d8d00a37c8e39b2557d23a26cc4f722595b4f2d9" +dependencies = [ + "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sourcefile" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "subtle" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "test_ffi" +version = "0.1.0" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "mockers 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mockers_derive 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-segmentation" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unindent" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "untrusted" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wasm-bindgen" +version = "0.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro-support 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wasm-bindgen-webidl" +version = "0.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "web-sys" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.27 (registry+https://github.com/rust-lang/crates.io-index)", + "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-webidl 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "webpki" +version = "0.21.0" +source = "git+https://github.com/geogriff-signal/webpki.git?rev=32ab63c46edfbfe9c1fe68008ba8e247eb387ca3#32ab63c46edfbfe9c1fe68008ba8e247eb387ca3" +dependencies = [ + "ring 0.16.9 (git+https://github.com/geogriff-signal/ring.git?rev=d0b3a4dcf1f24a2700280f9f7541cb5079398ec6)", + "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "weedle" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" +"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" +"checksum backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "690a62be8920ccf773ee00ef0968649b0e724cda8bd5b12286302b4ae955fdf5" +"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" +"checksum base64 0.10.1 (git+https://github.com/marshallpierce/rust-base64.git?rev=07b1d6b713cc2bd7d107185bd0d14bf06cddfb48)" = "" +"checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708" +"checksum bytes 0.5.0 (git+https://github.com/tokio-rs/bytes.git?rev=ebe96021b0eaf52be1fedd0a925f4384275c9cc4)" = "" +"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" +"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum chrono 0.4.9 (git+https://github.com/geogriff-signal/chrono.git?rev=de22e82a1b00b8f015a1d736e497c3177e8e8c9f)" = "" +"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum hashbrown 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bcea5b597dd98e6d1f1ec171744cc5dee1a30d1c23c5b98e3cf9d4fbdf8a526" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum indoc 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9553c1e16c114b8b77ebeb329e5f2876eed62a8d51178c8bc6bff0d65f98f8" +"checksum indoc-impl 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b714fc08d0961716390977cdff1536234415ac37b509e34e5a983def8340fb75" +"checksum intrusive-collections 0.8.1 (git+https://github.com/geogriff-signal/intrusive-rs.git?rev=3c14ea97598616a2beaa97cd5fbbb27ba574fe77)" = "" +"checksum itertools 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "c4a9b56eb56058f43dc66e58f40a214b2ccbc9f3df51861b63d51dec7b65bc3f" +"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum js-sys 0.3.27 (registry+https://github.com/rust-lang/crates.io-index)" = "1efc4f2a556c58e79c5500912e221dd826bec64ff4aabd8ce71ccef6da02d7d4" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" +"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" +"checksum memoffset 0.5.1 (git+https://github.com/Gilnaa/memoffset.git?rev=c14a1633760b55999a687bbdf17cb1fa3d1b2f9a)" = "" +"checksum mockers 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8bf36d14570661eebd299640fff1fae157c6d7da34bcbf4e4eba1c3b47b46ec9" +"checksum mockers_derive 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a53c3de46bf8e9cbe2b80e5086bbc8659bdf508c3719d50c2f366c9368d5726c" +"checksum no-std-compat 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df270209a7f04d62459240d890ecb792714d5db12c92937823574a09930276b4" +"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" +"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" +"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" +"checksum proc-macro-hack 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e688f31d92ffd7c1ddc57a1b4e6d773c0f2a14ee437a4b0a4f5a69c80eb221c8" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afdc77cc74ec70ed262262942ebb7dac3d479e9e5cfa2da1841c0806f6cdabcc" +"checksum proc-quote 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fa612543f23fda013e1e6ce30b5285a9d313c6e582e57b4ceca74eb5b85685b5" +"checksum proc-quote-impl 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f785f0f8cd00b7945efc3f3bdf8205eb06af5aacec598d83e67f41dc8d101fda" +"checksum prost 0.5.0 (git+https://github.com/geogriff-signal/prost.git?rev=907f7d6e714d0b449d75269d532561c71d3ed250)" = "" +"checksum prost-derive 0.5.0 (git+https://github.com/geogriff-signal/prost.git?rev=907f7d6e714d0b449d75269d532561c71d3ed250)" = "" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" +"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum ring 0.16.9 (git+https://github.com/geogriff-signal/ring.git?rev=d0b3a4dcf1f24a2700280f9f7541cb5079398ec6)" = "" +"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" +"checksum select-rustc 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fdb2e15b35a72de428af0da95ba4c990a336602576906f537923c5aa4d695835" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" +"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" +"checksum serde_json 1.0.40 (git+https://github.com/geogriff-signal/serde_json.git?rev=d79b0c67f62e168d4872bb8694377ffd97b8949f)" = "" +"checksum snow 0.6.1 (git+https://github.com/geogriff-signal/snow.git?rev=d8d00a37c8e39b2557d23a26cc4f722595b4f2d9)" = "" +"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" +"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +"checksum subtle 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "01f40907d9ffc762709e4ff3eb4a6f6b41b650375a3f09ac92b641942b7fb082" +"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" +"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" +"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum unindent 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c7d0d32a92c9ed197278e09140c32dec854ad5826f0e0e18c1d2a1690f15c8d5" +"checksum untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum wasm-bindgen 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "dcddca308b16cd93c2b67b126c688e5467e4ef2e28200dc7dfe4ae284f2faefc" +"checksum wasm-bindgen-backend 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "f805d9328b5fc7e5c6399960fd1889271b9b58ae17bdb2417472156cc9fafdd0" +"checksum wasm-bindgen-macro 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "3ff88201a482abfc63921621f6cb18eb1efd74f136b05e5841e7f8ca434539e9" +"checksum wasm-bindgen-macro-support 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "6a433d89ecdb9f77d46fcf00c8cf9f3467b7de9954d8710c175f61e2e245bb0e" +"checksum wasm-bindgen-shared 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "d41fc1bc3570cdf8d108c15e014045fd45a95bb5eb36605f96a90461fc34027d" +"checksum wasm-bindgen-webidl 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "be53d289bf2fa7645a089cfd5c7a34bf4fe94221f58cf86ee42a7b4bc854ff14" +"checksum web-sys 0.3.27 (registry+https://github.com/rust-lang/crates.io-index)" = "6435c477200ad486089a7a72c2bd6c9bdf9740bd7fff868806076218076d8c51" +"checksum webpki 0.21.0 (git+https://github.com/geogriff-signal/webpki.git?rev=32ab63c46edfbfe9c1fe68008ba8e247eb387ca3)" = "" +"checksum weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" +"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/enclave/Cargo.toml b/enclave/Cargo.toml new file mode 100644 index 0000000..91e478b --- /dev/null +++ b/enclave/Cargo.toml @@ -0,0 +1,10 @@ +[workspace] +members = ["kbupd_enclave", "sgx_ffi", "sgxsd_ffi", "test_ffi"] + +[patch.crates-io] +ring = { rev = "d0b3a4dcf1f24a2700280f9f7541cb5079398ec6", git = "https://github.com/geogriff-signal/ring.git" } +bytes = { rev = "ebe96021b0eaf52be1fedd0a925f4384275c9cc4", git = "https://github.com/tokio-rs/bytes.git" } +memoffset = { rev = "c14a1633760b55999a687bbdf17cb1fa3d1b2f9a", git = "https://github.com/Gilnaa/memoffset.git" } + +[profile.release] +lto = "fat" diff --git a/enclave/Makefile b/enclave/Makefile new file mode 100644 index 0000000..bf54746 --- /dev/null +++ b/enclave/Makefile @@ -0,0 +1,297 @@ +sgxsd_srcdir = sgxsd_enclave +includedir = include +patchdir = patches +builddir = build +targetdir = $(builddir)/target +resourcedir = ../service/kbupd/res + +RUSTC ?= rustc +CARGO ?= cargo +RUSTUP ?= rustup +BINDGEN ?= $(builddir)/bin/bindgen-0.51.1 +DOCKER ?= docker +INSTALL ?= install + +RUSTUP_TOOLCHAIN_UNSTABLE ?= nightly + +FEATURES ?= + +INSTALL_PROGRAM = $(INSTALL) -m 755 $(INSTALL_PROGRAM_FLAGS) +INSTALL_DATA = $(INSTALL) -m 644 + +DESTDIR ?= + +CFLAGS = \ + -m64 -O2 -ggdb -march=skylake -pipe -fPIC \ + -D_FORTIFY_SOURCE=2 -std=c11 -D_DEFAULT_SOURCE \ + -fstack-protector-strong -fcf-protection \ + -Wall -Werror=all -Wextra -Wno-unused-parameter -Wno-missing-field-initializers \ + -I$(includedir) -I$(includedir)/bearssl -I$(SGX_INCLUDEDIR) + +LDFLAGS = + +ENCLAVE_RUSTFLAGS = -C opt-level=2 -C debuginfo=1 -C codegen-units=1 -C panic=abort -Z allow_features=alloc_error_handler,thread_local -C llvm-args=-max-jump-table-size=1 -C llvm-args=-disable-tail-duplicate + +BINDGENFLAGS = +CARGOBUILDFLAGS = +export CARGO_TARGET_DIR = $(CURDIR)/$(targetdir) +export CARGO_HOME = $(CURDIR)/$(builddir)/cargo + +TEST_CFLAGS += $(CFLAGS) \ + -DUNIT_TESTING -fsanitize=address -static-libasan -fsanitize=undefined -static-libubsan + +## +## sgxsd +## + +TEST_SGXSD_TARGET := $(builddir)/sgxsd-enclave-test + +BEARSSL_SOURCES := \ + $(sgxsd_srcdir)/bearssl/gcm.c $(sgxsd_srcdir)/bearssl/ghash_pclmul.c \ + $(sgxsd_srcdir)/bearssl/sha2small.c $(sgxsd_srcdir)/bearssl/dec32be.c $(sgxsd_srcdir)/bearssl/enc32be.c \ + $(sgxsd_srcdir)/bearssl/aes_x86ni_ctr.c $(sgxsd_srcdir)/bearssl/aes_x86ni.c +BEARSSL_OBJECTS := $(addprefix $(builddir)/,$(BEARSSL_SOURCES:.c=.o)) + +SGXSD_SOURCES := $(sgxsd_srcdir)/sgxsd-enclave.c $(sgxsd_srcdir)/curve25519-donna-c64.c $(BEARSSL_SOURCES) \ + $(sgxsd_srcdir)/sgx-tcrypto-stub.c +SGXSD_OBJECTS := $(addprefix $(builddir)/,$(SGXSD_SOURCES:.c=.o)) + +TEST_SGXSD_SOURCES := $(sgxsd_srcdir)/sgxsd-enclave.c $(sgxsd_srcdir)/curve25519-donna-c64.c $(sgxsd_srcdir)/sgxsd-enclave-test.c $(sgxsd_srcdir)/cmockery.c +TEST_SGXSD_OBJECTS := $(addprefix $(builddir)/test/,$(TEST_SGXSD_SOURCES:.c=.o)) + +TEST_LDFLAGS += $(TEST_CFLAGS) + +## +## kbupd +## + +KBUPD_ENCLAVE_NAME := libkbupd_enclave.hardened +KBUPD_ENCLAVE_TARGET := $(builddir)/libkbupd_enclave.unstripped.so +KBUPD_ENCLAVE_RUST_STATICLIB := $(targetdir)/release/libkbupd_enclave.a + +## +## targets +## + +.PHONY: default docker-install all unstripped hardened unsigned llvm-bolt doc check test test-asan benchmark clippy bindgen protobuf debug sign install edger8r distclean clean docker + +.SUFFIXES: +.SUFFIXES: .c .o + +default: docker-install + +include sgx_enclave.mk + +docker-install: docker install + +all: $(KBUPD_ENCLAVE_TARGET) $(builddir)/$(KBUPD_ENCLAVE_NAME).unstripped.so $(builddir)/$(KBUPD_ENCLAVE_NAME).unsigned.so $(builddir)/$(KBUPD_ENCLAVE_NAME).debug.so $(builddir)/$(KBUPD_ENCLAVE_NAME).signdata $(builddir)/$(KBUPD_ENCLAVE_NAME).mrenclave + +unstripped: $(KBUPD_ENCLAVE_TARGET) + +hardened: $(builddir)/$(KBUPD_ENCLAVE_NAME).unstripped.so + +unsigned: $(builddir)/$(KBUPD_ENCLAVE_NAME).unsigned.so + +llvm-bolt: $(LLVM_BOLT) + +doc: + env -u CFLAGS RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(ENCLAVE_RUSTFLAGS)" \ + $(CARGO) doc --package=kbupd_enclave --release --document-private-items --lib + +check: + $(CARGO) check --package=kbupd_enclave --lib --tests + +test: $(TEST_SGXSD_TARGET) + ASAN_OPTIONS="detect_leaks=0:$(ASAN_OPTIONS)" ./$(TEST_SGXSD_TARGET) + env -u CFLAGS \ + RUST_BACKTRACE=full \ + RUST_TEST_THREADS=1 \ + $(CARGO) test --all --exclude=kbupd_enclave -- --test-threads=1 + env -u CFLAGS \ + RUST_BACKTRACE=full \ + RUST_TEST_THREADS=1 \ + $(CARGO) test --manifest-path=kbupd_enclave/Cargo.toml --lib --bins --features test -- --test-threads=1 + +test-asan: $(TEST_SGXSD_TARGET) + ./$(TEST_SGXSD_TARGET) + env -u CFLAGS \ + RUST_BACKTRACE=full \ + RUSTFLAGS="-Z sanitizer=address" RUST_TEST_THREADS=1 \ + ASAN_OPTIONS="detect_odr_violation=1:detect_stack_use_after_return=true:check_initialization_order=true:strict_init_order=true:halt_on_error=false:$(ASAN_OPTIONS)" \ + LSAN_OPTIONS="suppressions=$(CURDIR)/kbupd_enclave/lsan-ignore-test.txt:$(LSAN_OPTIONS)" \ + $(RUSTUP) run $(RUSTUP_TOOLCHAIN_UNSTABLE) \ + $(CARGO) test --all --exclude=kbupd_enclave -- --test-threads=1 + env -u CFLAGS \ + RUST_BACKTRACE=full \ + RUSTFLAGS="-Z sanitizer=address" RUST_TEST_THREADS=1 \ + ASAN_OPTIONS="detect_odr_violation=1:detect_stack_use_after_return=true:check_initialization_order=true:strict_init_order=true:halt_on_error=false:$(ASAN_OPTIONS)" \ + LSAN_OPTIONS="suppressions=$(CURDIR)/kbupd_enclave/lsan-ignore-test.txt:$(LSAN_OPTIONS)" \ + $(RUSTUP) run $(RUSTUP_TOOLCHAIN_UNSTABLE) \ + $(CARGO) test --manifest-path=kbupd_enclave/Cargo.toml --lib --bins --tests --features=test -- --test-threads=1 + +clippy: + $(CARGO) clippy --all --exclude=kbupd_enclave + $(CARGO) clippy --manifest-path=kbupd_enclave/Cargo.toml --features=test + +benchmark: + $(CARGO) bench --all + +bindgen: $(BINDGEN) | $(SGX_INCLUDEDIR) + $(BINDGEN) --no-include-path-detection -o sgx_ffi/src/bindgen_wrapper.rs \ + --rust-target 1.33 --use-core --ctypes-prefix libc --with-derive-default --with-derive-eq --no-prepend-enum-name \ + sgx_ffi/src/bindgen_wrapper.h -- \ + $(filter-out -fvisibility=hidden,$(filter-out -std=%,$(CFLAGS) $(ENCLAVE_CFLAGS))) + $(BINDGEN) --no-include-path-detection -o sgxsd_ffi/src/bindgen_wrapper.rs \ + --rust-target 1.33 --use-core --ctypes-prefix libc --with-derive-default --with-derive-eq --no-prepend-enum-name \ + sgxsd_ffi/src/bindgen_wrapper.h -- \ + $(filter-out -fvisibility=hidden,$(filter-out -std=%,$(CFLAGS) $(ENCLAVE_CFLAGS))) + $(BINDGEN) --no-include-path-detection -o kbupd_enclave/src/ffi/bindgen_wrapper.rs \ + --rust-target 1.33 --use-core --ctypes-prefix libc --with-derive-default --with-derive-eq --no-prepend-enum-name \ + kbupd_enclave/src/ffi/bindgen_wrapper.h -- \ + $(filter-out -fvisibility=hidden,$(filter-out -std=%,$(CFLAGS) $(ENCLAVE_CFLAGS))) + +$(builddir)/bin/bindgen-%: + $(CARGO) install --force --version $* --bin bindgen bindgen + mkdir -p $(builddir)/bin + cp $(builddir)/cargo/bin/bindgen $@ + +protobuf: | $(targetdir)/debug/prostc + mkdir -p kbupd_enclave/src/protobufs + OUT_DIR=kbupd_enclave/src/protobufs $(targetdir)/debug/prostc kbupd_enclave/src/protobufs.proto kbupd_enclave/src/ $(includedir)/ + mv kbupd_enclave/src/protobufs/protobufs.rs kbupd_enclave/src/protobufs/mod.rs + cd kbupd_enclave/src/protobufs; for file in protobufs.*.rs; do newfile=$${file#protobufs.}; mv $$file $$newfile; echo "pub mod $${newfile%.rs};" >> mod.rs; done + + +debug: $(builddir)/$(KBUPD_ENCLAVE_NAME).unsigned.so $(builddir)/$(KBUPD_ENCLAVE_NAME).debug.so + +sign: $(builddir)/$(KBUPD_ENCLAVE_NAME).signed.so $(builddir)/$(KBUPD_ENCLAVE_NAME).test.signed.so + +install: + $(INSTALL_DATA) $(builddir)/$(KBUPD_ENCLAVE_NAME).debug.so \ + $(resourcedir)/enclave/$$(cat $(builddir)/$(KBUPD_ENCLAVE_NAME).mrenclave).so + if [ -e $(builddir)/$(KBUPD_ENCLAVE_NAME).signed.so ]; then \ + cp $(builddir)/$(KBUPD_ENCLAVE_NAME).signed.so \ + $(resourcedir)/enclave/$$(cat $(builddir)/$(KBUPD_ENCLAVE_NAME).mrenclave).so; \ + fi + +edger8r: $(includedir)/kbupd_enclave.edl | $(SGX_EDGER8R) + $(SGX_EDGER8R) --untrusted --untrusted-dir $(includedir) \ + --trusted --trusted-dir $(includedir) \ + --search-path $(SGX_INCLUDEDIR) \ + --search-path $(includedir) \ + $(includedir)/kbupd_enclave.edl + +distclean: clean + +clean: + rm -f $(builddir)/$(KBUPD_ENCLAVE_NAME).debug.signdata \ + $(builddir)/$(KBUPD_ENCLAVE_NAME).debug.key \ + $(builddir)/$(KBUPD_ENCLAVE_NAME).debug.pub \ + $(builddir)/$(KBUPD_ENCLAVE_NAME).debug.sig \ + $(builddir)/$(KBUPD_ENCLAVE_NAME).test.sig \ + $(builddir)/$(KBUPD_ENCLAVE_NAME).test.signdata \ + $(builddir)/$(KBUPD_ENCLAVE_NAME).sig \ + $(builddir)/$(KBUPD_ENCLAVE_NAME).signdata \ + $(builddir)/$(KBUPD_ENCLAVE_NAME).mrenclave \ + $(builddir)/*.o \ + $(builddir)/*.a \ + $(builddir)/*.so \ + $(SGXSD_OBJECTS) $(TEST_SGXSD_TARGET) $(TEST_SGXSD_OBJECTS) \ + debian/debhelper-build-stamp \ + debian/kbupd-enclave.substvars \ + debian/files \ + debian/*.deb + -rm -r $(targetdir)/release/ \ + $(targetdir)/debug/ \ + $(builddir)/bolt/build \ + $(builddir)/cargo/bin \ + $(SGX_SDK_SOURCE_DIR) \ + debian/.debhelper/ \ + debian/kbupd-enclave/ + -$(CARGO) clean --release + +## rust + +.PHONY: FORCE +FORCE: + +$(targetdir)/debug/prostc: FORCE + env -u CFLAGS $(CARGO) build --manifest-path=prostc/Cargo.toml --bin=prostc +$(targetdir)/release/lib%.a: FORCE + env CFLAGS="" RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(ENCLAVE_RUSTFLAGS)" \ + $(CARGO) build -vv --release --package=$* --lib $(if $(FEATURES),--features $(FEATURES)) + +## sgxsd + +$(BEARSSL_OBJECTS): $(wildcard $(includedir)/bearssl/%.h) +$(SGXSD_OBJECTS): $(builddir)/%.o: %.c $(includedir)/sgxsd.h $(includedir)/sgxsd-enclave.h | $(SGX_INCLUDEDIR) + @mkdir -p $(dir $@) + $(CC) -o $@ $(CFLAGS) $(ENCLAVE_CFLAGS) -c $< + +$(TEST_SGXSD_TARGET): $(TEST_SGXSD_OBJECTS) + $(CC) -o $@ $(TEST_SGXSD_OBJECTS) $(TEST_LDFLAGS) +$(TEST_SGXSD_OBJECTS): $(builddir)/test/%.o: %.c $(includedir)/sgxsd.h $(includedir)/sgxsd-enclave.h $(includedir)/cmockery.h | $(SGX_INCLUDEDIR) + @mkdir -p $(dir $@) + $(CC) -o $@ $(CFLAGS) $(TEST_CFLAGS) -c $< + +## kbupd + +$(includedir)/kbupd_enclave_t.h $(includedir)/kbupd_enclave_u.h: $(includedir)/sgxsd.edl + +$(builddir)/kbupd_enclave_t.o: $(includedir)/kbupd_enclave_t.c + $(CC) -o $@ $(CFLAGS) $(ENCLAVE_CFLAGS) -c $< + +$(builddir)/kbupd_enclave_u.o: $(includedir)/kbupd_enclave_u.c + $(CC) -o $@ $(CFLAGS) -c $< + +$(builddir)/libkbupd_enclave_u.a: $(builddir)/kbupd_enclave_u.o + $(AR) r $@ $< + +$(KBUPD_ENCLAVE_TARGET): $(SGXSD_OBJECTS) $(KBUPD_ENCLAVE_RUST_STATICLIB) +$(KBUPD_ENCLAVE_TARGET): LDFLAGS := -L$(dir $(KBUPD_ENCLAVE_RUST_STATICLIB)) +$(KBUPD_ENCLAVE_TARGET): LDLIBS := -lkbupd_enclave + +## Reproducible enclave build via debian package + +MAKETARGET ?= bindgen debuild sign + +docker: DOCKER_EXTRA=$(shell [ -L build ] && P=$$(readlink build) && echo -v $$P/:$$P ) +docker: + $(DOCKER) build --build-arg UID=$$(id -u) --build-arg GID=$$(id -g) \ + -t kbupd-enclave-builder ./docker + $(DOCKER) run -it --rm --user $$(id -u):$$(id -g) --cap-add SYS_PTRACE \ + -v `pwd`/:/home/signal/src $(DOCKER_EXTRA) kbupd-enclave-builder \ + sh -c "cd src; make $(MAKETARGET)" + +.PHONY: debuild +debuild: + env -u LANG LC_ALL=C debuild --preserve-envvar=PATH --no-lintian --build=binary -uc -us -j1 + mv ../*.buildinfo debian/buildinfo + mv ../*.deb debian/ + +.PHONY: debuild-kbupd-enclave-build +debuild-kbupd-enclave-build: all + +.PHONY: debuild-kbupd-enclave-install +debuild-kbupd-enclave-install: $(builddir)/$(KBUPD_ENCLAVE_NAME).unsigned.so + mkdir -p $(DESTDIR)/usr/lib/kbupd/enclave/ + $(INSTALL_DATA) $(builddir)/$(KBUPD_ENCLAVE_NAME).unsigned.so $(DESTDIR)/usr/lib/kbupd/enclave/ + +.PHONY: debuild-kbupd-enclave-test +debuild-kbupd-enclave-test: + echo "not running tests in debuild" 1>&2 + +.PHONY: tar +tar: + tar -cjf $(builddir)/$(KBUPD_ENCLAVE_NAME).build.tar.bz2 \ + $(LLVM_BOLT) \ + --anchored --exclude-vcs \ + --exclude='$(builddir)/bolt' \ + --exclude='$(builddir)/cargo/registry/cache' \ + --exclude='$(builddir)/cargo/registry/index' \ + --exclude='$(builddir)/cargo/git/db' \ + --exclude='*.git' \ + --no-wildcards-match-slash \ + --exclude='$(builddir)/*.tar.bz2' \ + --verbose --totals \ + '$(builddir)/' diff --git a/enclave/bin/gc_functions b/enclave/bin/gc_functions new file mode 100755 index 0000000..d0dd7af --- /dev/null +++ b/enclave/bin/gc_functions @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 + +import sys, re, subprocess +from collections import namedtuple + +Function = namedtuple('Function', 'length length_instructions offset refs') + +start_func = re.compile(r'^([0-9a-f]+) <(.+)>:$') + +instruction = re.compile(r'^\s*[0-9a-f]+:\s+((?:[0-9a-f]{2} )+)\s*([0-9a-z()]+)?(?:\w+(.+))?\w*$') +instruction_ref = re.compile(r'^[^<]*(?:<([^>+]+)(?:[+]([^>]+))?>)?$') + +entry_point = re.compile(r'^\s*Entry point address:\s+0x([0-9a-f]+)$') + +data_line = re.compile(r'^\s*0x[0-9a-f]+ ([0-9a-f]+) ([0-9a-f]+) ([0-9a-f]+) ([0-9a-f]+) .+$') + +global_text = re.compile(r'^[0-9a-f]+ T (.+)$') + +def parse(disasm): + funcs = {} + + cur_func = None + cur_offset = 0 + cur_len = 0 + cur_len_instructions = 0 + cur_refs = None + for line in disasm: + line = line.rstrip('\n') + + if len(line) == 0: + continue + + if line.startswith('Disassembly of section '): + continue + + m = start_func.match(line) + if m: + if cur_func: + funcs[cur_func] = Function(length = cur_len, + length_instructions = cur_len_instructions, + offset = cur_offset, + refs = cur_refs) + cur_func = m.group(2) + cur_offset = m.group(1) + cur_len = 0 + cur_len_instructions = 0 + cur_refs = set() + + continue + + if not cur_func: + continue + + m = instruction.match(line) + if m: + instruction_bytes = m.group(1) + instruction_opcode = m.group(2) + instruction_operands = m.group(3) + if not instruction_opcode: + #Padding. As a hack decrement isns count, since it isn't really one. + cur_len_instructions -= 1 + if instruction_operands: + ref_match = instruction_ref.match(instruction_operands) + if ref_match: + ref = ref_match.group(1) + if ref: + cur_refs.add(ref.split('+')[0]) + cur_len_instructions += 1 + cur_len += instruction_bytes.count(' ') + else: + print("Couldn't parse '%s' " % line) + assert(False) + + if cur_func: + funcs[cur_func] = Function(length = cur_len, + length_instructions = cur_len_instructions, + offset = cur_offset, + refs = cur_refs) + + for key in tuple(funcs.keys()): + if key.startswith('.'): + del(funcs[key]) + + return funcs + +def get_roots(binary, funcs): + roots = set() + candidates = [] + + inv_funcs = {v.offset: k for k, v in funcs.items()} + + #Get the entry point, and scan various data sections for possible function pointers. + sections = [ '.data.rel.ro', '.rodata', '.data' ] + section_args = [ v for pair in zip(['-x'] * 3, sections) for v in pair ] + readelf = subprocess.run(['readelf', '-W', '-h', *section_args, binary], + stdout=subprocess.PIPE, universal_newlines=True) + for l in readelf.stdout.split('\n'): + m = entry_point.match(l) + if m: + roots.add(inv_funcs[m.group(1).rjust(16, '0')]) + + m = data_line.match(l) + if m: + #XXX This misses the trailing line if it has less than 16 bytes. + for i in 1, 3: + hi = m.group(i+1) + lo = m.group(i) + candidates.append("%s%s%s%s%s%s%s%s" % (hi[6:8], hi[4:6], hi[2:4], hi[0:2], + lo[6:8], lo[4:6], lo[2:4], lo[0:2])) + + for c in candidates: + f = inv_funcs.get(c) + if f: + roots.add(f) + + #Get exported .text segment symbols. + nm = subprocess.run(['nm', '-g', binary], stdout=subprocess.PIPE, universal_newlines=True) + for l in nm.stdout.split('\n'): + m = global_text.match(l) + if m: + roots.add(m.group(1)) + + return roots + +def mark(root, funcs, markset): +# if root == 'GFp_bn_sqr8x_internal': +# import inspect +# raise Exception([f.frame.f_locals['root'] for f in inspect.stack() if f.function == 'mark']) + markset.add(root) + for ref in funcs[root].refs: + if ref == root: + continue + if ref in markset: + continue + if ref in funcs: + mark(ref, funcs, markset) + +def clobber(binary, clobbers, funcs): + with open(binary, 'r+b') as binfile: + for clobber in clobbers: + func = funcs[clobber] + offset = int(func.offset, 16) + clobber_bytes = b'\xcc' * (func.length - 1) + clobber_bytes += b'\xc3' + binfile.seek(offset) + binfile.write(clobber_bytes) + +def main(): + assert(sys.argv[1].endswith('.so')) + + disasm = subprocess.Popen(['objdump', '-d', sys.argv[1]], + stdout=subprocess.PIPE, + universal_newlines=True) + funcs = parse(disasm.stdout) + + roots = get_roots(sys.argv[1], funcs) + print("Found %s roots" % len(roots)) + + marks = set() + for root in roots: + mark(root, funcs, marks) + + deads = set(funcs) - marks + + to_clobber = set() + byte_cnt = 0 + for dead in deads: + #if dead.startswith("_Z"): + # continue #Skip Rust code for now. + byte_cnt += funcs[dead].length + print("Dead: %s - %s isns %s:%s" % (dead, funcs[dead].length_instructions, + funcs[dead].offset, funcs[dead].length)) + to_clobber.add(dead) + + print('Dead/Total functions: %s/%s (%.2f%%)' % (len(to_clobber), len(funcs), + len(to_clobber)/len(funcs)*100.0)) + + byte_total = 0 + for f in funcs.values(): + byte_total += f.length + print('Dead bytes/Total bytes: %s/%s (%.2f%%)' % (byte_cnt, byte_total, + byte_cnt/byte_total*100.0)) + + if len(sys.argv) >= 3 and sys.argv[2] == '--clobber': + clobber(sys.argv[1], to_clobber, funcs) + print('Clobbered %s functions' % len(to_clobber)) + +if __name__ == '__main__': + main() diff --git a/enclave/bin/sgx-gdb b/enclave/bin/sgx-gdb new file mode 100755 index 0000000..4624310 --- /dev/null +++ b/enclave/bin/sgx-gdb @@ -0,0 +1,17 @@ +#!/bin/bash + +for dir in build/linux-sgx-* $(dir $0)/build/linux-sgx-*; do + if [ -d "$dir/build/linux" ]; then + SGX_SDK_SOURCE_DIR="$dir" + fi +done +if [ -z "$SGX_SDK_SOURCE_DIR" ]; then + echo "please set \$SGX_SDK_SOURCE_DIR" + exit 1 +fi + +GDB_SGX_PLUGIN_PATH=${SGX_SDK_SOURCE_DIR}/build/linux/gdb-sgx-plugin + +export PYTHONPATH=$GDB_SGX_PLUGIN_PATH +export SGX_DBG_OPTIN=1 +LD_PRELOAD="${SGX_SDK_SOURCE_DIR}/build/linux/libsgx_ptrace.so" gdb -iex "directory ${GDB_SGX_PLUGIN_PATH}" -iex "source ${GDB_SGX_PLUGIN_PATH}/gdb_sgx_plugin.py" -iex "set environment LD_PRELOAD" -iex "add-auto-load-safe-path /usr/lib" "$@" diff --git a/enclave/bolt_skip_funcs.txt b/enclave/bolt_skip_funcs.txt new file mode 100644 index 0000000..e69de29 diff --git a/enclave/ci/azure-pipelines/enclave.yml b/enclave/ci/azure-pipelines/enclave.yml new file mode 100644 index 0000000..e9a754c --- /dev/null +++ b/enclave/ci/azure-pipelines/enclave.yml @@ -0,0 +1,29 @@ +# +# Azure Pipeline to reproducibly build the backup enclave. Triggers on tags matching enclave-*. +# + +pr: none + +trigger: + branches: + include: + - refs/tags/enclave-* + +resources: + containers: + - container: kbupd-enclave-builder + image: signalbackupci:kbupd-enclave-builder + endpoint: signalbackupci-container-registry + options: --user 0:0 + +stages: +- stage: docker_build_kbupd_enclave_builder + displayName: docker build kbupd-enclave-builder + jobs: + - template: jobs/docker_build_kbupd_enclave_builder.yml + +- stage: make_all + displayName: make all + dependsOn: docker_build_kbupd_enclave_builder + jobs: + - template: jobs/make_all.yml diff --git a/enclave/ci/azure-pipelines/jobs/docker_build_kbupd_enclave_builder.yml b/enclave/ci/azure-pipelines/jobs/docker_build_kbupd_enclave_builder.yml new file mode 100644 index 0000000..37ca598 --- /dev/null +++ b/enclave/ci/azure-pipelines/jobs/docker_build_kbupd_enclave_builder.yml @@ -0,0 +1,38 @@ +# +# Azure Pipelines job to build the kbupd-enclave-builder docker image used to build the enclave +# + +jobs: +- job: docker_build_kbupd_enclave_builder + displayName: docker build kbupd-enclave-builder + pool: + vmImage: ubuntu-18.04 + steps: + - task: Docker@2 + displayName: docker login + inputs: + command: login + containerRegistry: signalbackupci-container-registry + + - script: docker pull signalbackupci.azurecr.io/signalbackupci:kbupd-enclave-builder || true + displayName: docker pull + + - task: Docker@2 + displayName: docker build + inputs: + command: build + dockerfile: enclave/docker/Dockerfile + arguments: --build-arg UID=1001 --build-arg GID=1001 --cache-from signalbackupci.azurecr.io/signalbackupci:kbupd-enclave-builder + repository: signalbackupci + tags: | + kbupd-enclave-builder + kbupd-enclave-builder-$(Build.SourceVersion) + + - task: Docker@2 + displayName: docker push + inputs: + command: push + repository: signalbackupci + tags: | + kbupd-enclave-builder + kbupd-enclave-builder-$(Build.SourceVersion) diff --git a/enclave/ci/azure-pipelines/jobs/make_all.yml b/enclave/ci/azure-pipelines/jobs/make_all.yml new file mode 100644 index 0000000..273bc69 --- /dev/null +++ b/enclave/ci/azure-pipelines/jobs/make_all.yml @@ -0,0 +1,52 @@ +# +# Azure Pipelines job to build the enclave from within in the kbupd-enclave-builder docker image. +# + +jobs: +- job: make_all + displayName: make all + pool: + vmImage: ubuntu-18.04 + container: kbupd-enclave-builder + timeoutInMinutes: 120 + steps: + - script: | + cp -a enclave /home/signal/src && \ + mkdir enclave/build/ && \ + ln -s `pwd`/enclave/build /home/signal/src/build + displayName: set up build directory + + - task: CacheBeta@1 + displayName: cached enclave/build/bin/ + inputs: + path: enclave/build/bin/ + key: enclave/docker/Dockerfile | enclave/docker/apt.conf | enclave/docker/sources.list | enclave/sgx_enclave.mk | "$(Build.SourceBranch)" + restoreKeys: + enclave/docker/Dockerfile | enclave/docker/apt.conf | enclave/docker/sources.list | enclave/sgx_enclave.mk | "refs/heads/$(System.PullRequest.TargetBranch)" + + enclave/docker/Dockerfile | enclave/docker/apt.conf | enclave/docker/sources.list | enclave/sgx_enclave.mk + + - script: make -C /home/signal/src/ bindgen + displayName: make bindgen + + - script: make -C /home/signal/src/ debuild + displayName: make debuild + + - publish: enclave/build/libkbupd_enclave.unstripped.so + artifact: libkbupd_enclave.unstripped.so + - publish: enclave/build/libkbupd_enclave.hardened.unstripped.so + artifact: libkbupd_enclave.hardened.unstripped.so + - publish: enclave/build/libkbupd_enclave.hardened.unsigned.so + artifact: libkbupd_enclave.hardened.unsigned.so + - publish: enclave/build/libkbupd_enclave.hardened.signdata + artifact: libkbupd_enclave.hardened.signdata + - publish: enclave/build/libkbupd_enclave.hardened.mrenclave + artifact: libkbupd_enclave.hardened.mrenclave + - publish: enclave/build/bin/llvm-bolt + artifact: llvm-bolt + + - script: make -C /home/signal/src/ tar + displayName: make tar + + - publish: enclave/build/libkbupd_enclave.hardened.build.tar.bz2 + artifact: libkbupd_enclave.hardened.build.tar.bz2 diff --git a/enclave/ci/azure-pipelines/jobs/make_test.yml b/enclave/ci/azure-pipelines/jobs/make_test.yml new file mode 100644 index 0000000..314ad4c --- /dev/null +++ b/enclave/ci/azure-pipelines/jobs/make_test.yml @@ -0,0 +1,13 @@ +# +# Azure Pipelines job to run all enclave tests from within in the kbupd-enclave-builder docker image. +# + +jobs: +- job: make_test + displayName: make test + pool: + vmImage: ubuntu-18.04 + container: kbupd-enclave-builder + steps: + - script: make -C enclave/ test + displayName: make test diff --git a/enclave/ci/azure-pipelines/master.yml b/enclave/ci/azure-pipelines/master.yml new file mode 100644 index 0000000..dc4a410 --- /dev/null +++ b/enclave/ci/azure-pipelines/master.yml @@ -0,0 +1,44 @@ +# +# Azure Pipeline to build & test the backup enclave. Triggers on commits and PRs to master in enclave/. +# + +pr: + branches: + include: + - master + paths: + include: + - enclave/ + +trigger: + branches: + include: + - master + paths: + include: + - enclave/ + +resources: + containers: + - container: kbupd-enclave-builder + image: signalbackupci:kbupd-enclave-builder + endpoint: signalbackupci-container-registry + options: --user 0:0 + +stages: +- stage: docker_build_kbupd_enclave_builder + displayName: docker build kbupd-enclave-builder + jobs: + - template: jobs/docker_build_kbupd_enclave_builder.yml + +- stage: make_test + displayName: make test + dependsOn: docker_build_kbupd_enclave_builder + jobs: + - template: jobs/make_test.yml + +- stage: make_all + displayName: make all + dependsOn: docker_build_kbupd_enclave_builder + jobs: + - template: jobs/make_all.yml diff --git a/enclave/debian/buildinfo b/enclave/debian/buildinfo new file mode 100644 index 0000000..4cafd4d --- /dev/null +++ b/enclave/debian/buildinfo @@ -0,0 +1,209 @@ +Format: 1.0 +Source: kbupd-enclave +Binary: kbupd-enclave +Architecture: amd64 +Version: 1.0 +Checksums-Md5: + 59ef215ccc0d2628535a2a92d45ec15d 583248 kbupd-enclave_1.0_amd64.deb +Checksums-Sha1: + e7ece57ea7b04ced54dbe4edb1d1d0d3eae1760d 583248 kbupd-enclave_1.0_amd64.deb +Checksums-Sha256: + 27fa91826311e79327241f0f067a819ebdaf1f8713ed33e19fa78362a457d218 583248 kbupd-enclave_1.0_amd64.deb +Build-Origin: Debian +Build-Architecture: amd64 +Build-Date: Mon, 18 Nov 2019 00:18:32 +0000 +Installed-Build-Depends: + autoconf (= 2.69-11), + automake (= 1:1.16.1-4), + autopoint (= 0.19.8.1-9), + autotools-dev (= 20180224.1), + base-files (= 10.3+deb10u1), + base-passwd (= 3.5.46), + bash (= 5.0-4), + binutils (= 2.31.1-16), + binutils-common (= 2.31.1-16), + binutils-x86-64-linux-gnu (= 2.31.1-16), + bsdmainutils (= 11.1.2+b1), + bsdutils (= 1:2.33.1-0.1), + build-essential (= 12.6), + bzip2 (= 1.0.6-9.2~deb10u1), + clang-7 (= 1:7.0.1-8), + coreutils (= 8.30-3), + cpp (= 4:8.3.0-1), + cpp-8 (= 8.3.0-6), + curl (= 7.64.0-4), + dash (= 0.5.10.2-5), + debconf (= 1.5.71), + debhelper (= 12.1.1), + debianutils (= 4.8.6.1), + dh-autoreconf (= 19), + dh-strip-nondeterminism (= 1.1.2-1), + diffutils (= 1:3.7-3), + dpkg (= 1.19.7), + dpkg-dev (= 1.19.7), + dwz (= 0.12-3), + fdisk (= 2.33.1-0.1), + file (= 1:5.35-4), + findutils (= 4.6.0+git+20190209-2), + g++ (= 4:8.3.0-1), + g++-8 (= 8.3.0-6), + gcc (= 4:8.3.0-1), + gcc-8 (= 8.3.0-6), + gcc-8-base (= 8.3.0-6), + gettext (= 0.19.8.1-9), + gettext-base (= 0.19.8.1-9), + git (= 1:2.20.1-2), + git-man (= 1:2.20.1-2), + grep (= 3.3-1), + groff-base (= 1.22.4-3), + gzip (= 1.9-3), + hostname (= 3.21), + init-system-helpers (= 1.56+nmu1), + intltool-debian (= 0.35.0+20060710.5), + lib32gcc1 (= 1:8.3.0-6), + lib32stdc++6 (= 8.3.0-6), + libacl1 (= 2.2.53-4), + libarchive-zip-perl (= 1.64-1), + libasan5 (= 8.3.0-6), + libatomic1 (= 8.3.0-6), + libattr1 (= 1:2.4.48-4), + libaudit-common (= 1:2.8.4-3), + libaudit1 (= 1:2.8.4-3), + libbinutils (= 2.31.1-16), + libblkid1 (= 2.33.1-0.1), + libbsd0 (= 0.9.1-2), + libbz2-1.0 (= 1.0.6-9.2~deb10u1), + libc-bin (= 2.28-10), + libc-dev-bin (= 2.28-10), + libc6 (= 2.28-10), + libc6-dev (= 2.28-10), + libc6-i386 (= 2.28-10), + libcap-ng0 (= 0.7.9-2), + libcc1-0 (= 8.3.0-6), + libclang-common-7-dev (= 1:7.0.1-8), + libclang1-7 (= 1:7.0.1-8), + libcom-err2 (= 1.44.5-1+deb10u2), + libcroco3 (= 0.6.12-3), + libcurl3-gnutls (= 7.64.0-4), + libcurl4 (= 7.64.0-4), + libcurl4-openssl-dev (= 7.64.0-4), + libdb5.3 (= 5.3.28+dfsg1-0.5), + libdebconfclient0 (= 0.249), + libdpkg-perl (= 1.19.7), + libedit2 (= 3.1-20181209-1), + libelf1 (= 0.176-1.1), + liberror-perl (= 0.17027-2), + libexpat1 (= 2.2.6-2+deb10u1), + libfdisk1 (= 2.33.1-0.1), + libffi6 (= 3.2.1-9), + libfile-stripnondeterminism-perl (= 1.1.2-1), + libgc1c2 (= 1:7.6.4-0.4), + libgcc-8-dev (= 8.3.0-6), + libgcc1 (= 1:8.3.0-6), + libgcrypt20 (= 1.8.4-5), + libgdbm-compat4 (= 1.18.1-4), + libgdbm6 (= 1.18.1-4), + libglib2.0-0 (= 2.58.3-2+deb10u1), + libgmp10 (= 2:6.1.2+dfsg-4), + libgnutls30 (= 3.6.7-4), + libgomp1 (= 8.3.0-6), + libgpg-error0 (= 1.35-1), + libgssapi-krb5-2 (= 1.17-3), + libhogweed4 (= 3.4.1-1), + libicu63 (= 63.1-6), + libidn2-0 (= 2.0.5-1), + libisl19 (= 0.20-2), + libitm1 (= 8.3.0-6), + libk5crypto3 (= 1.17-3), + libkeyutils1 (= 1.6-6), + libkrb5-3 (= 1.17-3), + libkrb5support0 (= 1.17-3), + libldap-2.4-2 (= 2.4.47+dfsg-3+deb10u1), + libldap-common (= 2.4.47+dfsg-3+deb10u1), + libllvm7 (= 1:7.0.1-8), + liblsan0 (= 8.3.0-6), + liblz4-1 (= 1.8.3-1), + liblzma5 (= 5.2.4-1), + libmagic-mgc (= 1:5.35-4), + libmagic1 (= 1:5.35-4), + libmount1 (= 2.33.1-0.1), + libmpc3 (= 1.1.0-1), + libmpfr6 (= 4.0.2-1), + libmpx2 (= 8.3.0-6), + libncurses-dev (= 6.1+20181013-2+deb10u1), + libncurses5-dev (= 6.1+20181013-2+deb10u1), + libncurses6 (= 6.1+20181013-2+deb10u1), + libncursesw6 (= 6.1+20181013-2+deb10u1), + libnettle6 (= 3.4.1-1), + libnghttp2-14 (= 1.36.0-2+deb10u1), + libobjc-8-dev (= 8.3.0-6), + libobjc4 (= 8.3.0-6), + libp11-kit0 (= 0.23.15-2), + libpam-modules (= 1.3.1-5), + libpam-modules-bin (= 1.3.1-5), + libpam-runtime (= 1.3.1-5), + libpam0g (= 1.3.1-5), + libpcre2-8-0 (= 10.32-5), + libpcre3 (= 2:8.39-12), + libperl5.28 (= 5.28.1-6), + libpipeline1 (= 1.5.1-2), + libprotobuf-dev (= 3.6.1.3-2), + libprotobuf-lite17 (= 3.6.1.3-2), + libprotobuf17 (= 3.6.1.3-2), + libpsl5 (= 0.20.2-2), + libquadmath0 (= 8.3.0-6), + librtmp1 (= 2.4+20151223.gitfa8646d.1-2), + libsasl2-2 (= 2.1.27+dfsg-1), + libsasl2-modules-db (= 2.1.27+dfsg-1), + libseccomp2 (= 2.3.3-4), + libselinux1 (= 2.8-1+b1), + libsigsegv2 (= 2.12-2), + libsmartcols1 (= 2.33.1-0.1), + libssh2-1 (= 1.8.0-2.1), + libssl-dev (= 1.1.1d-0+deb10u2), + libssl1.1 (= 1.1.1d-0+deb10u2), + libstdc++-8-dev (= 8.3.0-6), + libstdc++6 (= 8.3.0-6), + libsystemd0 (= 241-7~deb10u1), + libtasn1-6 (= 4.13-3), + libtinfo6 (= 6.1+20181013-2+deb10u1), + libtool (= 2.4.6-9), + libtsan0 (= 8.3.0-6), + libubsan1 (= 8.3.0-6), + libuchardet0 (= 0.0.6-3), + libudev1 (= 241-7~deb10u1), + libunistring2 (= 0.9.10-1), + libuuid1 (= 2.33.1-0.1), + libxml2 (= 2.9.4+dfsg1-7+b3), + linux-libc-dev (= 4.19.67-2+deb10u1), + login (= 1:4.5-1.1), + m4 (= 1.4.18-2), + make (= 4.2.1-1.2), + man-db (= 2.8.5-2), + mawk (= 1.3.3-17+b3), + ncurses-base (= 6.1+20181013-2+deb10u1), + ncurses-bin (= 6.1+20181013-2+deb10u1), + ocaml-base-nox (= 4.05.0-11), + ocaml-compiler-libs (= 4.05.0-11), + ocaml-interp (= 4.05.0-11), + ocaml-nox (= 4.05.0-11), + patch (= 2.7.6-3+deb10u1), + perl (= 5.28.1-6), + perl-base (= 5.28.1-6), + perl-modules-5.28 (= 5.28.1-6), + pkg-config (= 0.29-6), + po-debconf (= 1.0.21), + sed (= 4.7-1), + sensible-utils (= 0.0.12), + sysvinit-utils (= 2.93-8), + tar (= 1.30+dfsg-6), + util-linux (= 2.33.1-0.1), + wget (= 1.20.1-1.1), + xz-utils (= 5.2.4-1), + zlib1g (= 1:1.2.11.dfsg-1), + zlib1g-dev (= 1:1.2.11.dfsg-1) +Environment: + DEB_BUILD_OPTIONS="parallel=1" + LC_ALL="C" + MAKEFLAGS=" -j1" + SOURCE_DATE_EPOCH="1543475532" diff --git a/enclave/debian/changelog b/enclave/debian/changelog new file mode 100644 index 0000000..7b16fc0 --- /dev/null +++ b/enclave/debian/changelog @@ -0,0 +1,5 @@ +kbupd-enclave (1.0) unstable; urgency=medium + + * + + -- Jeffrey Griffin Wed, 28 Nov 2018 23:12:12 -0800 diff --git a/enclave/debian/compat b/enclave/debian/compat new file mode 100644 index 0000000..f599e28 --- /dev/null +++ b/enclave/debian/compat @@ -0,0 +1 @@ +10 diff --git a/enclave/debian/control b/enclave/debian/control new file mode 100644 index 0000000..a625ff8 --- /dev/null +++ b/enclave/debian/control @@ -0,0 +1,25 @@ +Source: kbupd-enclave +Section: database +Priority: optional +Maintainer: Jeffrey Griffin +Build-Depends: + debhelper (>= 10), + automake, + autoconf, + libtool, + curl, + wget, + git, + ocaml-native-compilers, + pkg-config, + libssl-dev, + libprotobuf-dev, + libcurl4-openssl-dev, +Standards-Version: 4.0.0 + +Package: kbupd-enclave +Architecture: amd64 +Depends: + ${misc:Depends}, +Description: Key Backup Service Enclave library + Signal's Key Backup Service Enclave library diff --git a/enclave/debian/copyright b/enclave/debian/copyright new file mode 100644 index 0000000..b44ba56 --- /dev/null +++ b/enclave/debian/copyright @@ -0,0 +1,668 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: kbupd-enclave +Source: + +Files: * +Copyright: Copyright (C) 2018 Open Whisper Systems +License: AGPL-3+ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/enclave/debian/rules b/enclave/debian/rules new file mode 100755 index 0000000..f85fda5 --- /dev/null +++ b/enclave/debian/rules @@ -0,0 +1,16 @@ +#!/usr/bin/make -f +export DH_VERBOSE = 1 +export DEB_BUILD_MAINT_OPTIONS=qa=-all sanitize=-all hardening=-all reproducible=-all parallel=1 +unexport LANG +export LC_ALL=C +%: + dh $@ --no-parallel + +override_dh_auto_build: + dh_auto_build -- debuild-kbupd-enclave-build + +override_dh_auto_install: + make DESTDIR=debian/kbupd-enclave debuild-kbupd-enclave-install + +override_dh_auto_test: + dh_auto_build -- debuild-kbupd-enclave-test diff --git a/enclave/debian/source/format b/enclave/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/enclave/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/enclave/debian/source/lintian-overrides b/enclave/debian/source/lintian-overrides new file mode 100644 index 0000000..0d7e534 --- /dev/null +++ b/enclave/debian/source/lintian-overrides @@ -0,0 +1,2 @@ +shared-lib-without-dependency-information usr/lib/kbupd/enclave/*.so +shlib-with-executable-stack usr/lib/kbupd/enclave/*.so diff --git a/enclave/docker/Dockerfile b/enclave/docker/Dockerfile new file mode 100644 index 0000000..3d3b2d4 --- /dev/null +++ b/enclave/docker/Dockerfile @@ -0,0 +1,42 @@ +FROM debian:buster-20191014 + +COPY apt.conf sources.list /etc/apt/ + +RUN apt-get update \ + && apt-get install -V -y --no-install-recommends --allow-downgrades \ + build-essential ca-certificates curl git cmake ninja-build \ + devscripts debhelper fakeroot libwww-perl gnupg \ + ocaml-native-compilers ocamlbuild automake autoconf libtool wget python pkg-config \ + libssl-dev libcurl4-openssl-dev protobuf-compiler libprotobuf-dev \ + llvm-dev libclang-dev clang \ + && rm -rf /var/lib/apt/lists/* + +ARG UID +ARG GID + +#Create a user to map the host user to. +RUN groupadd -o -g ${GID} signal \ + && useradd -m -o -u ${UID} -g ${GID} -s /bin/bash signal \ + && mkdir -p /tmp/docker \ + && chown -R signal.signal /tmp/docker + +USER signal +ENV HOME /home/signal +ENV USER signal +ENV SHELL /bin/bash + +WORKDIR /home/signal + +ARG TOOLCHAIN=1.37.0 + +COPY rustup-init.sha256 /tmp/docker/ + +RUN curl -f https://static.rust-lang.org/rustup/archive/1.20.2/x86_64-unknown-linux-gnu/rustup-init -o /tmp/rustup-init \ + && [ `sha256sum /tmp/rustup-init|cut -d' ' -f1` = `cut -d' ' -f1 + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_H__ +#define BR_BEARSSL_H__ + +#include +#include + +/** \mainpage BearSSL API + * + * # API Layout + * + * The functions and structures defined by the BearSSL API are located + * in various header files: + * + * | Header file | Elements | + * | :-------------- | :------------------------------------------------ | + * | bearssl_hash.h | Hash functions | + * | bearssl_hmac.h | HMAC | + * | bearssl_kdf.h | Key Derivation Functions | + * | bearssl_rand.h | Pseudorandom byte generators | + * | bearssl_prf.h | PRF implementations (for SSL/TLS) | + * | bearssl_block.h | Symmetric encryption | + * | bearssl_aead.h | AEAD algorithms (combined encryption + MAC) | + * | bearssl_rsa.h | RSA encryption and signatures | + * | bearssl_ec.h | Elliptic curves support (including ECDSA) | + * | bearssl_ssl.h | SSL/TLS engine interface | + * | bearssl_x509.h | X.509 certificate decoding and validation | + * | bearssl_pem.h | Base64/PEM decoding support functions | + * + * Applications using BearSSL are supposed to simply include `bearssl.h` + * as follows: + * + * #include + * + * The `bearssl.h` file itself includes all the other header files. It is + * possible to include specific header files, but it has no practical + * advantage for the application. The API is separated into separate + * header files only for documentation convenience. + * + * + * # Conventions + * + * ## MUST and SHALL + * + * In all descriptions, the usual "MUST", "SHALL", "MAY",... terminology + * is used. Failure to meet requirements expressed with a "MUST" or + * "SHALL" implies undefined behaviour, which means that segmentation + * faults, buffer overflows, and other similar adverse events, may occur. + * + * In general, BearSSL is not very forgiving of programming errors, and + * does not include much failsafes or error reporting when the problem + * does not arise from external transient conditions, and can be fixed + * only in the application code. This is done so in order to make the + * total code footprint lighter. + * + * + * ## `NULL` values + * + * Function parameters with a pointer type shall not be `NULL` unless + * explicitly authorised by the documentation. As an exception, when + * the pointer aims at a sequence of bytes and is accompanied with + * a length parameter, and the length is zero (meaning that there is + * no byte at all to retrieve), then the pointer may be `NULL` even if + * not explicitly allowed. + * + * + * ## Memory Allocation + * + * BearSSL does not perform dynamic memory allocation. This implies that + * for any functionality that requires a non-transient state, the caller + * is responsible for allocating the relevant context structure. Such + * allocation can be done in any appropriate area, including static data + * segments, the heap, and the stack, provided that proper alignment is + * respected. The header files define these context structures + * (including size and contents), so the C compiler should handle + * alignment automatically. + * + * Since there is no dynamic resource allocation, there is also nothing to + * release. When the calling code is done with a BearSSL feature, it + * may simple release the context structures it allocated itself, with + * no "close function" to call. If the context structures were allocated + * on the stack (as local variables), then even that release operation is + * implicit. + * + * + * ## Structure Contents + * + * Except when explicitly indicated, structure contents are opaque: they + * are included in the header files so that calling code may know the + * structure sizes and alignment requirements, but callers SHALL NOT + * access individual fields directly. For fields that are supposed to + * be read from or written to, the API defines accessor functions (the + * simplest of these accessor functions are defined as `static inline` + * functions, and the C compiler will optimise them away). + * + * + * # API Usage + * + * BearSSL usage for running a SSL/TLS client or server is described + * on the [BearSSL Web site](https://www.bearssl.org/api1.html). The + * BearSSL source archive also comes with sample code. + */ + +#include "bearssl_hash.h" +#include "bearssl_hmac.h" +#include "bearssl_kdf.h" +#include "bearssl_rand.h" +#include "bearssl_prf.h" +#include "bearssl_block.h" +#include "bearssl_aead.h" +#include "bearssl_rsa.h" +#include "bearssl_ec.h" +#include "bearssl_ssl.h" +#include "bearssl_x509.h" +#include "bearssl_pem.h" + +/** \brief Type for a configuration option. + * + * A "configuration option" is a value that is selected when the BearSSL + * library itself is compiled. Most options are boolean; their value is + * then either 1 (option is enabled) or 0 (option is disabled). Some + * values have other integer values. Option names correspond to macro + * names. Some of the options can be explicitly set in the internal + * `"config.h"` file. + */ +typedef struct { + /** \brief Configurable option name. */ + const char *name; + /** \brief Configurable option value. */ + long value; +} br_config_option; + +/** \brief Get configuration report. + * + * This function returns compiled configuration options, each as a + * 'long' value. Names match internal macro names, in particular those + * that can be set in the `"config.h"` inner file. For boolean options, + * the numerical value is 1 if enabled, 0 if disabled. For maximum + * key sizes, values are expressed in bits. + * + * The returned array is terminated by an entry whose `name` is `NULL`. + * + * \return the configuration report. + */ +const br_config_option *br_get_config(void); + +#endif diff --git a/enclave/include/bearssl/bearssl_aead.h b/enclave/include/bearssl/bearssl_aead.h new file mode 100644 index 0000000..8e35a1f --- /dev/null +++ b/enclave/include/bearssl/bearssl_aead.h @@ -0,0 +1,1059 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_AEAD_H__ +#define BR_BEARSSL_AEAD_H__ + +#include +#include + +#include "bearssl_block.h" +#include "bearssl_hash.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file bearssl_aead.h + * + * # Authenticated Encryption with Additional Data + * + * This file documents the API for AEAD encryption. + * + * + * ## Procedural API + * + * An AEAD algorithm processes messages and provides confidentiality + * (encryption) and checked integrity (MAC). It uses the following + * parameters: + * + * - A symmetric key. Exact size depends on the AEAD algorithm. + * + * - A nonce (IV). Size depends on the AEAD algorithm; for most + * algorithms, it is crucial for security that any given nonce + * value is never used twice for the same key and distinct + * messages. + * + * - Data to encrypt and protect. + * + * - Additional authenticated data, which is covered by the MAC but + * otherwise left untouched (i.e. not encrypted). + * + * The AEAD algorithm encrypts the data, and produces an authentication + * tag. It is assumed that the encrypted data, the tag, the additional + * authenticated data and the nonce are sent to the receiver; the + * additional data and the nonce may be implicit (e.g. using elements of + * the underlying transport protocol, such as record sequence numbers). + * The receiver will recompute the tag value and compare it with the one + * received; if they match, then the data is correct, and can be + * decrypted and used; otherwise, at least one of the elements was + * altered in transit, normally leading to wholesale rejection of the + * complete message. + * + * For each AEAD algorithm, identified by a symbolic name (hereafter + * denoted as "`xxx`"), the following functions are defined: + * + * - `br_xxx_init()` + * + * Initialise the AEAD algorithm, on a provided context structure. + * Exact parameters depend on the algorithm, and may include + * pointers to extra implementations and context structures. The + * secret key is provided at this point, either directly or + * indirectly. + * + * - `br_xxx_reset()` + * + * Start a new AEAD computation. The nonce value is provided as + * parameter to this function. + * + * - `br_xxx_aad_inject()` + * + * Inject some additional authenticated data. Additional data may + * be provided in several chunks of arbitrary length. + * + * - `br_xxx_flip()` + * + * This function MUST be called after injecting all additional + * authenticated data, and before beginning to encrypt the plaintext + * (or decrypt the ciphertext). + * + * - `br_xxx_run()` + * + * Process some plaintext (to encrypt) or ciphertext (to decrypt). + * Encryption/decryption is done in place. Data may be provided in + * several chunks of arbitrary length. + * + * - `br_xxx_get_tag()` + * + * Compute the authentication tag. All message data (encrypted or + * decrypted) must have been injected at that point. Also, this + * call may modify internal context elements, so it may be called + * only once for a given AEAD computation. + * + * - `br_xxx_check_tag()` + * + * An alternative to `br_xxx_get_tag()`, meant to be used by the + * receiver: the authentication tag is internally recomputed, and + * compared with the one provided as parameter. + * + * This API makes the following assumptions on the AEAD algorithm: + * + * - Encryption does not expand the size of the ciphertext; there is + * no padding. This is true of most modern AEAD modes such as GCM. + * + * - The additional authenticated data must be processed first, + * before the encrypted/decrypted data. + * + * - Nonce, plaintext and additional authenticated data all consist + * in an integral number of bytes. There is no provision to use + * elements whose length in bits is not a multiple of 8. + * + * Each AEAD algorithm has its own requirements and limits on the sizes + * of additional data and plaintext. This API does not provide any + * way to report invalid usage; it is up to the caller to ensure that + * the provided key, nonce, and data elements all fit the algorithm's + * requirements. + * + * + * ## Object-Oriented API + * + * Each context structure begins with a field (called `vtable`) that + * points to an instance of a structure that references the relevant + * functions through pointers. Each such structure contains the + * following: + * + * - `reset` + * + * Pointer to the reset function, that allows starting a new + * computation. + * + * - `aad_inject` + * + * Pointer to the additional authenticated data injection function. + * + * - `flip` + * + * Pointer to the function that transitions from additional data + * to main message data processing. + * + * - `get_tag` + * + * Pointer to the function that computes and returns the tag. + * + * - `check_tag` + * + * Pointer to the function that computes and verifies the tag against + * a received value. + * + * Note that there is no OOP method for context initialisation: the + * various AEAD algorithms have different requirements that would not + * map well to a single initialisation API. + * + * The OOP API is not provided for CCM, due to its specific requirements + * (length of plaintext must be known in advance). + */ + +/** + * \brief Class type of an AEAD algorithm. + */ +typedef struct br_aead_class_ br_aead_class; +struct br_aead_class_ { + + /** + * \brief Size (in bytes) of authentication tags created by + * this AEAD algorithm. + */ + size_t tag_size; + + /** + * \brief Reset an AEAD context. + * + * This function resets an already initialised AEAD context for + * a new computation run. Implementations and keys are + * conserved. This function can be called at any time; it + * cancels any ongoing AEAD computation that uses the provided + * context structure. + + * The provided IV is a _nonce_. Each AEAD algorithm has its + * own requirements on IV size and contents; for most of them, + * it is crucial to security that each nonce value is used + * only once for a given secret key. + * + * \param cc AEAD context structure. + * \param iv AEAD nonce to use. + * \param len AEAD nonce length (in bytes). + */ + void (*reset)(const br_aead_class **cc, const void *iv, size_t len); + + /** + * \brief Inject additional authenticated data. + * + * The provided data is injected into a running AEAD + * computation. Additional data must be injected _before_ the + * call to `flip()`. Additional data can be injected in several + * chunks of arbitrary length. + * + * \param cc AEAD context structure. + * \param data pointer to additional authenticated data. + * \param len length of additional authenticated data (in bytes). + */ + void (*aad_inject)(const br_aead_class **cc, + const void *data, size_t len); + + /** + * \brief Finish injection of additional authenticated data. + * + * This function MUST be called before beginning the actual + * encryption or decryption (with `run()`), even if no + * additional authenticated data was injected. No additional + * authenticated data may be injected after this function call. + * + * \param cc AEAD context structure. + */ + void (*flip)(const br_aead_class **cc); + + /** + * \brief Encrypt or decrypt some data. + * + * Data encryption or decryption can be done after `flip()` has + * been called on the context. If `encrypt` is non-zero, then + * the provided data shall be plaintext, and it is encrypted in + * place. Otherwise, the data shall be ciphertext, and it is + * decrypted in place. + * + * Data may be provided in several chunks of arbitrary length. + * + * \param cc AEAD context structure. + * \param encrypt non-zero for encryption, zero for decryption. + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + */ + void (*run)(const br_aead_class **cc, int encrypt, + void *data, size_t len); + + /** + * \brief Compute authentication tag. + * + * Compute the AEAD authentication tag. The tag length depends + * on the AEAD algorithm; it is written in the provided `tag` + * buffer. This call terminates the AEAD run: no data may be + * processed with that AEAD context afterwards, until `reset()` + * is called to initiate a new AEAD run. + * + * The tag value must normally be sent along with the encrypted + * data. When decrypting, the tag value must be recomputed and + * compared with the received tag: if the two tag values differ, + * then either the tag or the encrypted data was altered in + * transit. As an alternative to this function, the + * `check_tag()` function may be used to compute and check the + * tag value. + * + * Tag length depends on the AEAD algorithm. + * + * \param cc AEAD context structure. + * \param tag destination buffer for the tag. + */ + void (*get_tag)(const br_aead_class **cc, void *tag); + + /** + * \brief Compute and check authentication tag. + * + * This function is an alternative to `get_tag()`, and is + * normally used on the receiving end (i.e. when decrypting + * messages). The tag value is recomputed and compared with the + * provided tag value. If they match, 1 is returned; on + * mismatch, 0 is returned. A returned value of 0 means that the + * data or the tag was altered in transit, normally leading to + * wholesale rejection of the complete message. + * + * Tag length depends on the AEAD algorithm. + * + * \param cc AEAD context structure. + * \param tag tag value to compare with. + * \return 1 on success (exact match of tag value), 0 otherwise. + */ + uint32_t (*check_tag)(const br_aead_class **cc, const void *tag); + + /** + * \brief Compute authentication tag (with truncation). + * + * This function is similar to `get_tag()`, except that the tag + * length is provided. Some AEAD algorithms allow several tag + * lengths, usually by truncating the normal tag. Shorter tags + * mechanically increase success probability of forgeries. + * The range of allowed tag lengths depends on the algorithm. + * + * \param cc AEAD context structure. + * \param tag destination buffer for the tag. + * \param len tag length (in bytes). + */ + void (*get_tag_trunc)(const br_aead_class **cc, void *tag, size_t len); + + /** + * \brief Compute and check authentication tag (with truncation). + * + * This function is similar to `check_tag()` except that it + * works over an explicit tag length. See `get_tag()` for a + * discussion of explicit tag lengths; the range of allowed tag + * lengths depends on the algorithm. + * + * \param cc AEAD context structure. + * \param tag tag value to compare with. + * \param len tag length (in bytes). + * \return 1 on success (exact match of tag value), 0 otherwise. + */ + uint32_t (*check_tag_trunc)(const br_aead_class **cc, + const void *tag, size_t len); +}; + +/** + * \brief Context structure for GCM. + * + * GCM is an AEAD mode that combines a block cipher in CTR mode with a + * MAC based on GHASH, to provide authenticated encryption: + * + * - Any block cipher with 16-byte blocks can be used with GCM. + * + * - The nonce can have any length, from 0 up to 2^64-1 bits; however, + * 96-bit nonces (12 bytes) are recommended (nonces with a length + * distinct from 12 bytes are internally hashed, which risks reusing + * nonce value with a small but not always negligible probability). + * + * - Additional authenticated data may have length up to 2^64-1 bits. + * + * - Message length may range up to 2^39-256 bits at most. + * + * - The authentication tag has length 16 bytes. + * + * The GCM initialisation function receives as parameter an + * _initialised_ block cipher implementation context, with the secret + * key already set. A pointer to that context will be kept within the + * GCM context structure. It is up to the caller to allocate and + * initialise that block cipher context. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_aead_class *vtable; + +#ifndef BR_DOXYGEN_IGNORE + const br_block_ctr_class **bctx; + br_ghash gh; + unsigned char h[16]; + unsigned char j0_1[12]; + unsigned char buf[16]; + unsigned char y[16]; + uint32_t j0_2, jc; + uint64_t count_aad, count_ctr; +#endif +} br_gcm_context; + +/** + * \brief Initialize a GCM context. + * + * A block cipher implementation, with its initialised context structure, + * is provided. The block cipher MUST use 16-byte blocks in CTR mode, + * and its secret key MUST have been already set in the provided context. + * A GHASH implementation must also be provided. The parameters are linked + * in the GCM context. + * + * After this function has been called, the `br_gcm_reset()` function must + * be called, to provide the IV for GCM computation. + * + * \param ctx GCM context structure. + * \param bctx block cipher context (already initialised with secret key). + * \param gh GHASH implementation. + */ +void br_gcm_init(br_gcm_context *ctx, + const br_block_ctr_class **bctx, br_ghash gh); + +/** + * \brief Reset a GCM context. + * + * This function resets an already initialised GCM context for a new + * computation run. Implementations and keys are conserved. This function + * can be called at any time; it cancels any ongoing GCM computation that + * uses the provided context structure. + * + * The provided IV is a _nonce_. It is critical to GCM security that IV + * values are not repeated for the same encryption key. IV can have + * arbitrary length (up to 2^64-1 bits), but the "normal" length is + * 96 bits (12 bytes). + * + * \param ctx GCM context structure. + * \param iv GCM nonce to use. + * \param len GCM nonce length (in bytes). + */ +void br_gcm_reset(br_gcm_context *ctx, const void *iv, size_t len); + +/** + * \brief Inject additional authenticated data into GCM. + * + * The provided data is injected into a running GCM computation. Additional + * data must be injected _before_ the call to `br_gcm_flip()`. + * Additional data can be injected in several chunks of arbitrary length; + * the maximum total size of additional authenticated data is 2^64-1 + * bits. + * + * \param ctx GCM context structure. + * \param data pointer to additional authenticated data. + * \param len length of additional authenticated data (in bytes). + */ +void br_gcm_aad_inject(br_gcm_context *ctx, const void *data, size_t len); + +/** + * \brief Finish injection of additional authenticated data into GCM. + * + * This function MUST be called before beginning the actual encryption + * or decryption (with `br_gcm_run()`), even if no additional authenticated + * data was injected. No additional authenticated data may be injected + * after this function call. + * + * \param ctx GCM context structure. + */ +void br_gcm_flip(br_gcm_context *ctx); + +/** + * \brief Encrypt or decrypt some data with GCM. + * + * Data encryption or decryption can be done after `br_gcm_flip()` + * has been called on the context. If `encrypt` is non-zero, then the + * provided data shall be plaintext, and it is encrypted in place. + * Otherwise, the data shall be ciphertext, and it is decrypted in place. + * + * Data may be provided in several chunks of arbitrary length. The maximum + * total length for data is 2^39-256 bits, i.e. about 65 gigabytes. + * + * \param ctx GCM context structure. + * \param encrypt non-zero for encryption, zero for decryption. + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + */ +void br_gcm_run(br_gcm_context *ctx, int encrypt, void *data, size_t len); + +/** + * \brief Compute GCM authentication tag. + * + * Compute the GCM authentication tag. The tag is a 16-byte value which + * is written in the provided `tag` buffer. This call terminates the + * GCM run: no data may be processed with that GCM context afterwards, + * until `br_gcm_reset()` is called to initiate a new GCM run. + * + * The tag value must normally be sent along with the encrypted data. + * When decrypting, the tag value must be recomputed and compared with + * the received tag: if the two tag values differ, then either the tag + * or the encrypted data was altered in transit. As an alternative to + * this function, the `br_gcm_check_tag()` function can be used to + * compute and check the tag value. + * + * \param ctx GCM context structure. + * \param tag destination buffer for the tag (16 bytes). + */ +void br_gcm_get_tag(br_gcm_context *ctx, void *tag); + +/** + * \brief Compute and check GCM authentication tag. + * + * This function is an alternative to `br_gcm_get_tag()`, normally used + * on the receiving end (i.e. when decrypting value). The tag value is + * recomputed and compared with the provided tag value. If they match, 1 + * is returned; on mismatch, 0 is returned. A returned value of 0 means + * that the data or the tag was altered in transit, normally leading to + * wholesale rejection of the complete message. + * + * \param ctx GCM context structure. + * \param tag tag value to compare with (16 bytes). + * \return 1 on success (exact match of tag value), 0 otherwise. + */ +uint32_t br_gcm_check_tag(br_gcm_context *ctx, const void *tag); + +/** + * \brief Compute GCM authentication tag (with truncation). + * + * This function is similar to `br_gcm_get_tag()`, except that it allows + * the tag to be truncated to a smaller length. The intended tag length + * is provided as `len` (in bytes); it MUST be no more than 16, but + * it may be smaller. Note that decreasing tag length mechanically makes + * forgeries easier; NIST SP 800-38D specifies that the tag length shall + * lie between 12 and 16 bytes (inclusive), but may be truncated down to + * 4 or 8 bytes, for specific applications that can tolerate it. It must + * also be noted that successful forgeries leak information on the + * authentication key, making subsequent forgeries easier. Therefore, + * tag truncation, and in particular truncation to sizes lower than 12 + * bytes, shall be envisioned only with great care. + * + * The tag is written in the provided `tag` buffer. This call terminates + * the GCM run: no data may be processed with that GCM context + * afterwards, until `br_gcm_reset()` is called to initiate a new GCM + * run. + * + * The tag value must normally be sent along with the encrypted data. + * When decrypting, the tag value must be recomputed and compared with + * the received tag: if the two tag values differ, then either the tag + * or the encrypted data was altered in transit. As an alternative to + * this function, the `br_gcm_check_tag_trunc()` function can be used to + * compute and check the tag value. + * + * \param ctx GCM context structure. + * \param tag destination buffer for the tag. + * \param len tag length (16 bytes or less). + */ +void br_gcm_get_tag_trunc(br_gcm_context *ctx, void *tag, size_t len); + +/** + * \brief Compute and check GCM authentication tag (with truncation). + * + * This function is an alternative to `br_gcm_get_tag_trunc()`, normally used + * on the receiving end (i.e. when decrypting value). The tag value is + * recomputed and compared with the provided tag value. If they match, 1 + * is returned; on mismatch, 0 is returned. A returned value of 0 means + * that the data or the tag was altered in transit, normally leading to + * wholesale rejection of the complete message. + * + * Tag length MUST be 16 bytes or less. The normal GCM tag length is 16 + * bytes. See `br_check_tag_trunc()` for some discussion on the potential + * perils of truncating authentication tags. + * + * \param ctx GCM context structure. + * \param tag tag value to compare with. + * \param len tag length (in bytes). + * \return 1 on success (exact match of tag value), 0 otherwise. + */ +uint32_t br_gcm_check_tag_trunc(br_gcm_context *ctx, + const void *tag, size_t len); + +/** + * \brief Class instance for GCM. + */ +extern const br_aead_class br_gcm_vtable; + +/** + * \brief Context structure for EAX. + * + * EAX is an AEAD mode that combines a block cipher in CTR mode with + * CBC-MAC using the same block cipher and the same key, to provide + * authenticated encryption: + * + * - Any block cipher with 16-byte blocks can be used with EAX + * (technically, other block sizes are defined as well, but this + * is not implemented by these functions; shorter blocks also + * imply numerous security issues). + * + * - The nonce can have any length, as long as nonce values are + * not reused (thus, if nonces are randomly selected, the nonce + * size should be such that reuse probability is negligible). + * + * - Additional authenticated data length is unlimited. + * + * - Message length is unlimited. + * + * - The authentication tag has length 16 bytes. + * + * The EAX initialisation function receives as parameter an + * _initialised_ block cipher implementation context, with the secret + * key already set. A pointer to that context will be kept within the + * EAX context structure. It is up to the caller to allocate and + * initialise that block cipher context. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_aead_class *vtable; + +#ifndef BR_DOXYGEN_IGNORE + const br_block_ctrcbc_class **bctx; + unsigned char L2[16]; + unsigned char L4[16]; + unsigned char nonce[16]; + unsigned char head[16]; + unsigned char ctr[16]; + unsigned char cbcmac[16]; + unsigned char buf[16]; + size_t ptr; +#endif +} br_eax_context; + +/** + * \brief EAX captured state. + * + * Some internal values computed by EAX may be captured at various + * points, and reused for another EAX run with the same secret key, + * for lower per-message overhead. Captured values do not depend on + * the nonce. + */ +typedef struct { +#ifndef BR_DOXYGEN_IGNORE + unsigned char st[3][16]; +#endif +} br_eax_state; + +/** + * \brief Initialize an EAX context. + * + * A block cipher implementation, with its initialised context + * structure, is provided. The block cipher MUST use 16-byte blocks in + * CTR + CBC-MAC mode, and its secret key MUST have been already set in + * the provided context. The parameters are linked in the EAX context. + * + * After this function has been called, the `br_eax_reset()` function must + * be called, to provide the nonce for EAX computation. + * + * \param ctx EAX context structure. + * \param bctx block cipher context (already initialised with secret key). + */ +void br_eax_init(br_eax_context *ctx, const br_block_ctrcbc_class **bctx); + +/** + * \brief Capture pre-AAD state. + * + * This function precomputes key-dependent data, and stores it in the + * provided `st` structure. This structure should then be used with + * `br_eax_reset_pre_aad()`, or updated with `br_eax_get_aad_mac()` + * and then used with `br_eax_reset_post_aad()`. + * + * The EAX context structure is unmodified by this call. + * + * \param ctx EAX context structure. + * \param st recipient for captured state. + */ +void br_eax_capture(const br_eax_context *ctx, br_eax_state *st); + +/** + * \brief Reset an EAX context. + * + * This function resets an already initialised EAX context for a new + * computation run. Implementations and keys are conserved. This function + * can be called at any time; it cancels any ongoing EAX computation that + * uses the provided context structure. + * + * It is critical to EAX security that nonce values are not repeated for + * the same encryption key. Nonces can have arbitrary length. If nonces + * are randomly generated, then a nonce length of at least 128 bits (16 + * bytes) is recommended, to make nonce reuse probability sufficiently + * low. + * + * \param ctx EAX context structure. + * \param nonce EAX nonce to use. + * \param len EAX nonce length (in bytes). + */ +void br_eax_reset(br_eax_context *ctx, const void *nonce, size_t len); + +/** + * \brief Reset an EAX context with a pre-AAD captured state. + * + * This function is an alternative to `br_eax_reset()`, that reuses a + * previously captured state structure for lower per-message overhead. + * The state should have been populated with `br_eax_capture_state()` + * but not updated with `br_eax_get_aad_mac()`. + * + * After this function is called, additional authenticated data MUST + * be injected. At least one byte of additional authenticated data + * MUST be provided with `br_eax_aad_inject()`; computation result will + * be incorrect if `br_eax_flip()` is called right away. + * + * After injection of the AAD and call to `br_eax_flip()`, at least + * one message byte must be provided. Empty messages are not supported + * with this reset mode. + * + * \param ctx EAX context structure. + * \param st pre-AAD captured state. + * \param nonce EAX nonce to use. + * \param len EAX nonce length (in bytes). + */ +void br_eax_reset_pre_aad(br_eax_context *ctx, const br_eax_state *st, + const void *nonce, size_t len); + +/** + * \brief Reset an EAX context with a post-AAD captured state. + * + * This function is an alternative to `br_eax_reset()`, that reuses a + * previously captured state structure for lower per-message overhead. + * The state should have been populated with `br_eax_capture_state()` + * and then updated with `br_eax_get_aad_mac()`. + * + * After this function is called, message data MUST be injected. The + * `br_eax_flip()` function MUST NOT be called. At least one byte of + * message data MUST be provided with `br_eax_run()`; empty messages + * are not supported with this reset mode. + * + * \param ctx EAX context structure. + * \param st post-AAD captured state. + * \param nonce EAX nonce to use. + * \param len EAX nonce length (in bytes). + */ +void br_eax_reset_post_aad(br_eax_context *ctx, const br_eax_state *st, + const void *nonce, size_t len); + +/** + * \brief Inject additional authenticated data into EAX. + * + * The provided data is injected into a running EAX computation. Additional + * data must be injected _before_ the call to `br_eax_flip()`. + * Additional data can be injected in several chunks of arbitrary length; + * the total amount of additional authenticated data is unlimited. + * + * \param ctx EAX context structure. + * \param data pointer to additional authenticated data. + * \param len length of additional authenticated data (in bytes). + */ +void br_eax_aad_inject(br_eax_context *ctx, const void *data, size_t len); + +/** + * \brief Finish injection of additional authenticated data into EAX. + * + * This function MUST be called before beginning the actual encryption + * or decryption (with `br_eax_run()`), even if no additional authenticated + * data was injected. No additional authenticated data may be injected + * after this function call. + * + * \param ctx EAX context structure. + */ +void br_eax_flip(br_eax_context *ctx); + +/** + * \brief Obtain a copy of the MAC on additional authenticated data. + * + * This function may be called only after `br_eax_flip()`; it copies the + * AAD-specific MAC value into the provided state. The MAC value depends + * on the secret key and the additional data itself, but not on the + * nonce. The updated state `st` is meant to be used as parameter for a + * further `br_eax_reset_post_aad()` call. + * + * \param ctx EAX context structure. + * \param st captured state to update. + */ +static inline void +br_eax_get_aad_mac(const br_eax_context *ctx, br_eax_state *st) +{ + memcpy(st->st[1], ctx->head, sizeof ctx->head); +} + +/** + * \brief Encrypt or decrypt some data with EAX. + * + * Data encryption or decryption can be done after `br_eax_flip()` + * has been called on the context. If `encrypt` is non-zero, then the + * provided data shall be plaintext, and it is encrypted in place. + * Otherwise, the data shall be ciphertext, and it is decrypted in place. + * + * Data may be provided in several chunks of arbitrary length. + * + * \param ctx EAX context structure. + * \param encrypt non-zero for encryption, zero for decryption. + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + */ +void br_eax_run(br_eax_context *ctx, int encrypt, void *data, size_t len); + +/** + * \brief Compute EAX authentication tag. + * + * Compute the EAX authentication tag. The tag is a 16-byte value which + * is written in the provided `tag` buffer. This call terminates the + * EAX run: no data may be processed with that EAX context afterwards, + * until `br_eax_reset()` is called to initiate a new EAX run. + * + * The tag value must normally be sent along with the encrypted data. + * When decrypting, the tag value must be recomputed and compared with + * the received tag: if the two tag values differ, then either the tag + * or the encrypted data was altered in transit. As an alternative to + * this function, the `br_eax_check_tag()` function can be used to + * compute and check the tag value. + * + * \param ctx EAX context structure. + * \param tag destination buffer for the tag (16 bytes). + */ +void br_eax_get_tag(br_eax_context *ctx, void *tag); + +/** + * \brief Compute and check EAX authentication tag. + * + * This function is an alternative to `br_eax_get_tag()`, normally used + * on the receiving end (i.e. when decrypting value). The tag value is + * recomputed and compared with the provided tag value. If they match, 1 + * is returned; on mismatch, 0 is returned. A returned value of 0 means + * that the data or the tag was altered in transit, normally leading to + * wholesale rejection of the complete message. + * + * \param ctx EAX context structure. + * \param tag tag value to compare with (16 bytes). + * \return 1 on success (exact match of tag value), 0 otherwise. + */ +uint32_t br_eax_check_tag(br_eax_context *ctx, const void *tag); + +/** + * \brief Compute EAX authentication tag (with truncation). + * + * This function is similar to `br_eax_get_tag()`, except that it allows + * the tag to be truncated to a smaller length. The intended tag length + * is provided as `len` (in bytes); it MUST be no more than 16, but + * it may be smaller. Note that decreasing tag length mechanically makes + * forgeries easier; NIST SP 800-38D specifies that the tag length shall + * lie between 12 and 16 bytes (inclusive), but may be truncated down to + * 4 or 8 bytes, for specific applications that can tolerate it. It must + * also be noted that successful forgeries leak information on the + * authentication key, making subsequent forgeries easier. Therefore, + * tag truncation, and in particular truncation to sizes lower than 12 + * bytes, shall be envisioned only with great care. + * + * The tag is written in the provided `tag` buffer. This call terminates + * the EAX run: no data may be processed with that EAX context + * afterwards, until `br_eax_reset()` is called to initiate a new EAX + * run. + * + * The tag value must normally be sent along with the encrypted data. + * When decrypting, the tag value must be recomputed and compared with + * the received tag: if the two tag values differ, then either the tag + * or the encrypted data was altered in transit. As an alternative to + * this function, the `br_eax_check_tag_trunc()` function can be used to + * compute and check the tag value. + * + * \param ctx EAX context structure. + * \param tag destination buffer for the tag. + * \param len tag length (16 bytes or less). + */ +void br_eax_get_tag_trunc(br_eax_context *ctx, void *tag, size_t len); + +/** + * \brief Compute and check EAX authentication tag (with truncation). + * + * This function is an alternative to `br_eax_get_tag_trunc()`, normally used + * on the receiving end (i.e. when decrypting value). The tag value is + * recomputed and compared with the provided tag value. If they match, 1 + * is returned; on mismatch, 0 is returned. A returned value of 0 means + * that the data or the tag was altered in transit, normally leading to + * wholesale rejection of the complete message. + * + * Tag length MUST be 16 bytes or less. The normal EAX tag length is 16 + * bytes. See `br_check_tag_trunc()` for some discussion on the potential + * perils of truncating authentication tags. + * + * \param ctx EAX context structure. + * \param tag tag value to compare with. + * \param len tag length (in bytes). + * \return 1 on success (exact match of tag value), 0 otherwise. + */ +uint32_t br_eax_check_tag_trunc(br_eax_context *ctx, + const void *tag, size_t len); + +/** + * \brief Class instance for EAX. + */ +extern const br_aead_class br_eax_vtable; + +/** + * \brief Context structure for CCM. + * + * CCM is an AEAD mode that combines a block cipher in CTR mode with + * CBC-MAC using the same block cipher and the same key, to provide + * authenticated encryption: + * + * - Any block cipher with 16-byte blocks can be used with CCM + * (technically, other block sizes are defined as well, but this + * is not implemented by these functions; shorter blocks also + * imply numerous security issues). + * + * - The authentication tag length, and plaintext length, MUST be + * known when starting processing data. Plaintext and ciphertext + * can still be provided by chunks, but the total size must match + * the value provided upon initialisation. + * + * - The nonce length is constrained between 7 and 13 bytes (inclusive). + * Furthermore, the plaintext length, when encoded, must fit over + * 15-nonceLen bytes; thus, if the nonce has length 13 bytes, then + * the plaintext length cannot exceed 65535 bytes. + * + * - Additional authenticated data length is practically unlimited + * (formal limit is at 2^64 bytes). + * + * - The authentication tag has length 4 to 16 bytes (even values only). + * + * The CCM initialisation function receives as parameter an + * _initialised_ block cipher implementation context, with the secret + * key already set. A pointer to that context will be kept within the + * CCM context structure. It is up to the caller to allocate and + * initialise that block cipher context. + */ +typedef struct { +#ifndef BR_DOXYGEN_IGNORE + const br_block_ctrcbc_class **bctx; + unsigned char ctr[16]; + unsigned char cbcmac[16]; + unsigned char tagmask[16]; + unsigned char buf[16]; + size_t ptr; + size_t tag_len; +#endif +} br_ccm_context; + +/** + * \brief Initialize a CCM context. + * + * A block cipher implementation, with its initialised context + * structure, is provided. The block cipher MUST use 16-byte blocks in + * CTR + CBC-MAC mode, and its secret key MUST have been already set in + * the provided context. The parameters are linked in the CCM context. + * + * After this function has been called, the `br_ccm_reset()` function must + * be called, to provide the nonce for CCM computation. + * + * \param ctx CCM context structure. + * \param bctx block cipher context (already initialised with secret key). + */ +void br_ccm_init(br_ccm_context *ctx, const br_block_ctrcbc_class **bctx); + +/** + * \brief Reset a CCM context. + * + * This function resets an already initialised CCM context for a new + * computation run. Implementations and keys are conserved. This function + * can be called at any time; it cancels any ongoing CCM computation that + * uses the provided context structure. + * + * The `aad_len` parameter contains the total length, in bytes, of the + * additional authenticated data. It may be zero. That length MUST be + * exact. + * + * The `data_len` parameter contains the total length, in bytes, of the + * data that will be injected (plaintext or ciphertext). That length MUST + * be exact. Moreover, that length MUST be less than 2^(8*(15-nonce_len)). + * + * The nonce length (`nonce_len`), in bytes, must be in the 7..13 range + * (inclusive). + * + * The tag length (`tag_len`), in bytes, must be in the 4..16 range, and + * be an even integer. Short tags mechanically allow for higher forgery + * probabilities; hence, tag sizes smaller than 12 bytes shall be used only + * with care. + * + * It is critical to CCM security that nonce values are not repeated for + * the same encryption key. Random generation of nonces is not generally + * recommended, due to the relatively small maximum nonce value. + * + * Returned value is 1 on success, 0 on error. An error is reported if + * the tag or nonce length is out of range, or if the + * plaintext/ciphertext length cannot be encoded with the specified + * nonce length. + * + * \param ctx CCM context structure. + * \param nonce CCM nonce to use. + * \param nonce_len CCM nonce length (in bytes, 7 to 13). + * \param aad_len additional authenticated data length (in bytes). + * \param data_len plaintext/ciphertext length (in bytes). + * \param tag_len tag length (in bytes). + * \return 1 on success, 0 on error. + */ +int br_ccm_reset(br_ccm_context *ctx, const void *nonce, size_t nonce_len, + uint64_t aad_len, uint64_t data_len, size_t tag_len); + +/** + * \brief Inject additional authenticated data into CCM. + * + * The provided data is injected into a running CCM computation. Additional + * data must be injected _before_ the call to `br_ccm_flip()`. + * Additional data can be injected in several chunks of arbitrary length, + * but the total amount MUST exactly match the value which was provided + * to `br_ccm_reset()`. + * + * \param ctx CCM context structure. + * \param data pointer to additional authenticated data. + * \param len length of additional authenticated data (in bytes). + */ +void br_ccm_aad_inject(br_ccm_context *ctx, const void *data, size_t len); + +/** + * \brief Finish injection of additional authenticated data into CCM. + * + * This function MUST be called before beginning the actual encryption + * or decryption (with `br_ccm_run()`), even if no additional authenticated + * data was injected. No additional authenticated data may be injected + * after this function call. + * + * \param ctx CCM context structure. + */ +void br_ccm_flip(br_ccm_context *ctx); + +/** + * \brief Encrypt or decrypt some data with CCM. + * + * Data encryption or decryption can be done after `br_ccm_flip()` + * has been called on the context. If `encrypt` is non-zero, then the + * provided data shall be plaintext, and it is encrypted in place. + * Otherwise, the data shall be ciphertext, and it is decrypted in place. + * + * Data may be provided in several chunks of arbitrary length, provided + * that the total length exactly matches the length provided to the + * `br_ccm_reset()` call. + * + * \param ctx CCM context structure. + * \param encrypt non-zero for encryption, zero for decryption. + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + */ +void br_ccm_run(br_ccm_context *ctx, int encrypt, void *data, size_t len); + +/** + * \brief Compute CCM authentication tag. + * + * Compute the CCM authentication tag. This call terminates the CCM + * run: all data must have been injected with `br_ccm_run()` (in zero, + * one or more successive calls). After this function has been called, + * no more data can br processed; a `br_ccm_reset()` call is required + * to start a new message. + * + * The tag length was provided upon context initialisation (last call + * to `br_ccm_reset()`); it is returned by this function. + * + * The tag value must normally be sent along with the encrypted data. + * When decrypting, the tag value must be recomputed and compared with + * the received tag: if the two tag values differ, then either the tag + * or the encrypted data was altered in transit. As an alternative to + * this function, the `br_ccm_check_tag()` function can be used to + * compute and check the tag value. + * + * \param ctx CCM context structure. + * \param tag destination buffer for the tag (up to 16 bytes). + * \return the tag length (in bytes). + */ +size_t br_ccm_get_tag(br_ccm_context *ctx, void *tag); + +/** + * \brief Compute and check CCM authentication tag. + * + * This function is an alternative to `br_ccm_get_tag()`, normally used + * on the receiving end (i.e. when decrypting value). The tag value is + * recomputed and compared with the provided tag value. If they match, 1 + * is returned; on mismatch, 0 is returned. A returned value of 0 means + * that the data or the tag was altered in transit, normally leading to + * wholesale rejection of the complete message. + * + * \param ctx CCM context structure. + * \param tag tag value to compare with (up to 16 bytes). + * \return 1 on success (exact match of tag value), 0 otherwise. + */ +uint32_t br_ccm_check_tag(br_ccm_context *ctx, const void *tag); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/enclave/include/bearssl/bearssl_block.h b/enclave/include/bearssl/bearssl_block.h new file mode 100644 index 0000000..683a490 --- /dev/null +++ b/enclave/include/bearssl/bearssl_block.h @@ -0,0 +1,2618 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_BLOCK_H__ +#define BR_BEARSSL_BLOCK_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file bearssl_block.h + * + * # Block Ciphers and Symmetric Ciphers + * + * This file documents the API for block ciphers and other symmetric + * ciphers. + * + * + * ## Procedural API + * + * For a block cipher implementation, up to three separate sets of + * functions are provided, for CBC encryption, CBC decryption, and CTR + * encryption/decryption. Each set has its own context structure, + * initialised with the encryption key. + * + * For CBC encryption and decryption, the data to encrypt or decrypt is + * referenced as a sequence of blocks. The implementations assume that + * there is no partial block; no padding is applied or removed. The + * caller is responsible for handling any kind of padding. + * + * Function for CTR encryption are defined only for block ciphers with + * blocks of 16 bytes or more (i.e. AES, but not DES/3DES). + * + * Each implemented block cipher is identified by an "internal name" + * from which are derived the names of structures and functions that + * implement the cipher. For the block cipher of internal name "`xxx`", + * the following are defined: + * + * - `br_xxx_BLOCK_SIZE` + * + * A macro that evaluates to the block size (in bytes) of the + * cipher. For all implemented block ciphers, this value is a + * power of two. + * + * - `br_xxx_cbcenc_keys` + * + * Context structure that contains the subkeys resulting from the key + * expansion. These subkeys are appropriate for CBC encryption. The + * structure first field is called `vtable` and points to the + * appropriate OOP structure. + * + * - `br_xxx_cbcenc_init(br_xxx_cbcenc_keys *ctx, const void *key, size_t len)` + * + * Perform key expansion: subkeys for CBC encryption are computed and + * written in the provided context structure. The key length MUST be + * adequate for the implemented block cipher. This function also sets + * the `vtable` field. + * + * - `br_xxx_cbcenc_run(const br_xxx_cbcenc_keys *ctx, void *iv, void *data, size_t len)` + * + * Perform CBC encryption of `len` bytes, in place. The encrypted data + * replaces the cleartext. `len` MUST be a multiple of the block length + * (if it is not, the function may loop forever or overflow a buffer). + * The IV is provided with the `iv` pointer; it is also updated with + * a copy of the last encrypted block. + * + * - `br_xxx_cbcdec_keys` + * + * Context structure that contains the subkeys resulting from the key + * expansion. These subkeys are appropriate for CBC decryption. The + * structure first field is called `vtable` and points to the + * appropriate OOP structure. + * + * - `br_xxx_cbcdec_init(br_xxx_cbcenc_keys *ctx, const void *key, size_t len)` + * + * Perform key expansion: subkeys for CBC decryption are computed and + * written in the provided context structure. The key length MUST be + * adequate for the implemented block cipher. This function also sets + * the `vtable` field. + * + * - `br_xxx_cbcdec_run(const br_xxx_cbcdec_keys *ctx, void *iv, void *data, size_t num_blocks)` + * + * Perform CBC decryption of `len` bytes, in place. The decrypted data + * replaces the ciphertext. `len` MUST be a multiple of the block length + * (if it is not, the function may loop forever or overflow a buffer). + * The IV is provided with the `iv` pointer; it is also updated with + * a copy of the last _encrypted_ block. + * + * - `br_xxx_ctr_keys` + * + * Context structure that contains the subkeys resulting from the key + * expansion. These subkeys are appropriate for CTR encryption and + * decryption. The structure first field is called `vtable` and + * points to the appropriate OOP structure. + * + * - `br_xxx_ctr_init(br_xxx_ctr_keys *ctx, const void *key, size_t len)` + * + * Perform key expansion: subkeys for CTR encryption and decryption + * are computed and written in the provided context structure. The + * key length MUST be adequate for the implemented block cipher. This + * function also sets the `vtable` field. + * + * - `br_xxx_ctr_run(const br_xxx_ctr_keys *ctx, const void *iv, uint32_t cc, void *data, size_t len)` (returns `uint32_t`) + * + * Perform CTR encryption/decryption of some data. Processing is done + * "in place" (the output data replaces the input data). This function + * implements the "standard incrementing function" from NIST SP800-38A, + * annex B: the IV length shall be 4 bytes less than the block size + * (i.e. 12 bytes for AES) and the counter is the 32-bit value starting + * with `cc`. The data length (`len`) is not necessarily a multiple of + * the block size. The new counter value is returned, which supports + * chunked processing, provided that each chunk length (except possibly + * the last one) is a multiple of the block size. + * + * - `br_xxx_ctrcbc_keys` + * + * Context structure that contains the subkeys resulting from the + * key expansion. These subkeys are appropriate for doing combined + * CTR encryption/decryption and CBC-MAC, as used in the CCM and EAX + * authenticated encryption modes. The structure first field is + * called `vtable` and points to the appropriate OOP structure. + * + * - `br_xxx_ctrcbc_init(br_xxx_ctr_keys *ctx, const void *key, size_t len)` + * + * Perform key expansion: subkeys for combined CTR + * encryption/decryption and CBC-MAC are computed and written in the + * provided context structure. The key length MUST be adequate for + * the implemented block cipher. This function also sets the + * `vtable` field. + * + * - `br_xxx_ctrcbc_encrypt(const br_xxx_ctrcbc_keys *ctx, void *ctr, void *cbcmac, void *data, size_t len)` + * + * Perform CTR encryption of some data, and CBC-MAC. Processing is + * done "in place" (the output data replaces the input data). This + * function applies CTR encryption on the data, using a full + * block-size counter (i.e. for 128-bit blocks, the counter is + * incremented as a 128-bit value). The 'ctr' array contains the + * initial value for the counter (used in the first block) and it is + * updated with the new value after data processing. The 'cbcmac' + * value shall point to a block-sized value which is used as IV for + * CBC-MAC, computed over the encrypted data (output of CTR + * encryption); the resulting CBC-MAC is written over 'cbcmac' on + * output. + * + * The data length MUST be a multiple of the block size. + * + * - `br_xxx_ctrcbc_decrypt(const br_xxx_ctrcbc_keys *ctx, void *ctr, void *cbcmac, void *data, size_t len)` + * + * Perform CTR decryption of some data, and CBC-MAC. Processing is + * done "in place" (the output data replaces the input data). This + * function applies CTR decryption on the data, using a full + * block-size counter (i.e. for 128-bit blocks, the counter is + * incremented as a 128-bit value). The 'ctr' array contains the + * initial value for the counter (used in the first block) and it is + * updated with the new value after data processing. The 'cbcmac' + * value shall point to a block-sized value which is used as IV for + * CBC-MAC, computed over the encrypted data (input of CTR + * encryption); the resulting CBC-MAC is written over 'cbcmac' on + * output. + * + * The data length MUST be a multiple of the block size. + * + * - `br_xxx_ctrcbc_ctr(const br_xxx_ctrcbc_keys *ctx, void *ctr, void *data, size_t len)` + * + * Perform CTR encryption or decryption of the provided data. The + * data is processed "in place" (the output data replaces the input + * data). A full block-sized counter is applied (i.e. for 128-bit + * blocks, the counter is incremented as a 128-bit value). The 'ctr' + * array contains the initial value for the counter (used in the + * first block), and it is updated with the new value after data + * processing. + * + * The data length MUST be a multiple of the block size. + * + * - `br_xxx_ctrcbc_mac(const br_xxx_ctrcbc_keys *ctx, void *cbcmac, const void *data, size_t len)` + * + * Compute CBC-MAC over the provided data. The IV for CBC-MAC is + * provided as 'cbcmac'; the output is written over the same array. + * The data itself is untouched. The data length MUST be a multiple + * of the block size. + * + * + * It shall be noted that the key expansion functions return `void`. If + * the provided key length is not allowed, then there will be no error + * reporting; implementations need not validate the key length, thus an + * invalid key length may result in undefined behaviour (e.g. buffer + * overflow). + * + * Subkey structures contain no interior pointer, and no external + * resources are allocated upon key expansion. They can thus be + * discarded without any explicit deallocation. + * + * + * ## Object-Oriented API + * + * Each context structure begins with a field (called `vtable`) that + * points to an instance of a structure that references the relevant + * functions through pointers. Each such structure contains the + * following: + * + * - `context_size` + * + * The size (in bytes) of the context structure for subkeys. + * + * - `block_size` + * + * The cipher block size (in bytes). + * + * - `log_block_size` + * + * The base-2 logarithm of cipher block size (e.g. 4 for blocks + * of 16 bytes). + * + * - `init` + * + * Pointer to the key expansion function. + * + * - `run` + * + * Pointer to the encryption/decryption function. + * + * For combined CTR/CBC-MAC encryption, the `vtable` has a slightly + * different structure: + * + * - `context_size` + * + * The size (in bytes) of the context structure for subkeys. + * + * - `block_size` + * + * The cipher block size (in bytes). + * + * - `log_block_size` + * + * The base-2 logarithm of cipher block size (e.g. 4 for blocks + * of 16 bytes). + * + * - `init` + * + * Pointer to the key expansion function. + * + * - `encrypt` + * + * Pointer to the CTR encryption + CBC-MAC function. + * + * - `decrypt` + * + * Pointer to the CTR decryption + CBC-MAC function. + * + * - `ctr` + * + * Pointer to the CTR encryption/decryption function. + * + * - `mac` + * + * Pointer to the CBC-MAC function. + * + * For block cipher "`xxx`", static, constant instances of these + * structures are defined, under the names: + * + * - `br_xxx_cbcenc_vtable` + * - `br_xxx_cbcdec_vtable` + * - `br_xxx_ctr_vtable` + * - `br_xxx_ctrcbc_vtable` + * + * + * ## Implemented Block Ciphers + * + * Provided implementations are: + * + * | Name | Function | Block Size (bytes) | Key lengths (bytes) | + * | :-------- | :------- | :----------------: | :-----------------: | + * | aes_big | AES | 16 | 16, 24 and 32 | + * | aes_small | AES | 16 | 16, 24 and 32 | + * | aes_ct | AES | 16 | 16, 24 and 32 | + * | aes_ct64 | AES | 16 | 16, 24 and 32 | + * | aes_x86ni | AES | 16 | 16, 24 and 32 | + * | aes_pwr8 | AES | 16 | 16, 24 and 32 | + * | des_ct | DES/3DES | 8 | 8, 16 and 24 | + * | des_tab | DES/3DES | 8 | 8, 16 and 24 | + * + * **Note:** DES/3DES nominally uses keys of 64, 128 and 192 bits (i.e. 8, + * 16 and 24 bytes), but some of the bits are ignored by the algorithm, so + * the _effective_ key lengths, from a security point of view, are 56, + * 112 and 168 bits, respectively. + * + * `aes_big` is a "classical" AES implementation, using tables. It + * is fast but not constant-time, since it makes data-dependent array + * accesses. + * + * `aes_small` is an AES implementation optimized for code size. It + * is substantially slower than `aes_big`; it is not constant-time + * either. + * + * `aes_ct` is a constant-time implementation of AES; its code is about + * as big as that of `aes_big`, while its performance is comparable to + * that of `aes_small`. However, it is constant-time. This + * implementation should thus be considered to be the "default" AES in + * BearSSL, to be used unless the operational context guarantees that a + * non-constant-time implementation is safe, or an architecture-specific + * constant-time implementation can be used (e.g. using dedicated + * hardware opcodes). + * + * `aes_ct64` is another constant-time implementation of AES. It is + * similar to `aes_ct` but uses 64-bit values. On 32-bit machines, + * `aes_ct64` is not faster than `aes_ct`, often a bit slower, and has + * a larger footprint; however, on 64-bit architectures, `aes_ct64` + * is typically twice faster than `aes_ct` for modes that allow parallel + * operations (i.e. CTR, and CBC decryption, but not CBC encryption). + * + * `aes_x86ni` exists only on x86 architectures (32-bit and 64-bit). It + * uses the AES-NI opcodes when available. + * + * `aes_pwr8` exists only on PowerPC / POWER architectures (32-bit and + * 64-bit, both little-endian and big-endian). It uses the AES opcodes + * present in POWER8 and later. + * + * `des_tab` is a classic, table-based implementation of DES/3DES. It + * is not constant-time. + * + * `des_ct` is an constant-time implementation of DES/3DES. It is + * substantially slower than `des_tab`. + * + * ## ChaCha20 and Poly1305 + * + * ChaCha20 is a stream cipher. Poly1305 is a MAC algorithm. They + * are described in [RFC 7539](https://tools.ietf.org/html/rfc7539). + * + * Two function pointer types are defined: + * + * - `br_chacha20_run` describes a function that implements ChaCha20 + * only. + * + * - `br_poly1305_run` describes an implementation of Poly1305, + * in the AEAD combination with ChaCha20 specified in RFC 7539 + * (the ChaCha20 implementation is provided as a function pointer). + * + * `chacha20_ct` is a straightforward implementation of ChaCha20 in + * plain C; it is constant-time, small, and reasonably fast. + * + * `chacha20_sse2` leverages SSE2 opcodes (on x86 architectures that + * support these opcodes). It is faster than `chacha20_ct`. + * + * `poly1305_ctmul` is an implementation of the ChaCha20+Poly1305 AEAD + * construction, where the Poly1305 part is performed with mixed 32-bit + * multiplications (operands are 32-bit, result is 64-bit). + * + * `poly1305_ctmul32` implements ChaCha20+Poly1305 using pure 32-bit + * multiplications (32-bit operands, 32-bit result). It is slower than + * `poly1305_ctmul`, except on some specific architectures such as + * the ARM Cortex M0+. + * + * `poly1305_ctmulq` implements ChaCha20+Poly1305 with mixed 64-bit + * multiplications (operands are 64-bit, result is 128-bit) on 64-bit + * platforms that support such operations. + * + * `poly1305_i15` implements ChaCha20+Poly1305 with the generic "i15" + * big integer implementation. It is meant mostly for testing purposes, + * although it can help with saving a few hundred bytes of code footprint + * on systems where code size is scarce. + */ + +/** + * \brief Class type for CBC encryption implementations. + * + * A `br_block_cbcenc_class` instance points to the functions implementing + * a specific block cipher, when used in CBC mode for encrypting data. + */ +typedef struct br_block_cbcenc_class_ br_block_cbcenc_class; +struct br_block_cbcenc_class_ { + /** + * \brief Size (in bytes) of the context structure appropriate + * for containing subkeys. + */ + size_t context_size; + + /** + * \brief Size of individual blocks (in bytes). + */ + unsigned block_size; + + /** + * \brief Base-2 logarithm of the size of individual blocks, + * expressed in bytes. + */ + unsigned log_block_size; + + /** + * \brief Initialisation function. + * + * This function sets the `vtable` field in the context structure. + * The key length MUST be one of the key lengths supported by + * the implementation. + * + * \param ctx context structure to initialise. + * \param key secret key. + * \param key_len key length (in bytes). + */ + void (*init)(const br_block_cbcenc_class **ctx, + const void *key, size_t key_len); + + /** + * \brief Run the CBC encryption. + * + * The `iv` parameter points to the IV for this run; it is + * updated with a copy of the last encrypted block. The data + * is encrypted "in place"; its length (`len`) MUST be a + * multiple of the block size. + * + * \param ctx context structure (already initialised). + * \param iv IV for CBC encryption (updated). + * \param data data to encrypt. + * \param len data length (in bytes, multiple of block size). + */ + void (*run)(const br_block_cbcenc_class *const *ctx, + void *iv, void *data, size_t len); +}; + +/** + * \brief Class type for CBC decryption implementations. + * + * A `br_block_cbcdec_class` instance points to the functions implementing + * a specific block cipher, when used in CBC mode for decrypting data. + */ +typedef struct br_block_cbcdec_class_ br_block_cbcdec_class; +struct br_block_cbcdec_class_ { + /** + * \brief Size (in bytes) of the context structure appropriate + * for containing subkeys. + */ + size_t context_size; + + /** + * \brief Size of individual blocks (in bytes). + */ + unsigned block_size; + + /** + * \brief Base-2 logarithm of the size of individual blocks, + * expressed in bytes. + */ + unsigned log_block_size; + + /** + * \brief Initialisation function. + * + * This function sets the `vtable` field in the context structure. + * The key length MUST be one of the key lengths supported by + * the implementation. + * + * \param ctx context structure to initialise. + * \param key secret key. + * \param key_len key length (in bytes). + */ + void (*init)(const br_block_cbcdec_class **ctx, + const void *key, size_t key_len); + + /** + * \brief Run the CBC decryption. + * + * The `iv` parameter points to the IV for this run; it is + * updated with a copy of the last encrypted block. The data + * is decrypted "in place"; its length (`len`) MUST be a + * multiple of the block size. + * + * \param ctx context structure (already initialised). + * \param iv IV for CBC decryption (updated). + * \param data data to decrypt. + * \param len data length (in bytes, multiple of block size). + */ + void (*run)(const br_block_cbcdec_class *const *ctx, + void *iv, void *data, size_t len); +}; + +/** + * \brief Class type for CTR encryption/decryption implementations. + * + * A `br_block_ctr_class` instance points to the functions implementing + * a specific block cipher, when used in CTR mode for encrypting or + * decrypting data. + */ +typedef struct br_block_ctr_class_ br_block_ctr_class; +struct br_block_ctr_class_ { + /** + * \brief Size (in bytes) of the context structure appropriate + * for containing subkeys. + */ + size_t context_size; + + /** + * \brief Size of individual blocks (in bytes). + */ + unsigned block_size; + + /** + * \brief Base-2 logarithm of the size of individual blocks, + * expressed in bytes. + */ + unsigned log_block_size; + + /** + * \brief Initialisation function. + * + * This function sets the `vtable` field in the context structure. + * The key length MUST be one of the key lengths supported by + * the implementation. + * + * \param ctx context structure to initialise. + * \param key secret key. + * \param key_len key length (in bytes). + */ + void (*init)(const br_block_ctr_class **ctx, + const void *key, size_t key_len); + + /** + * \brief Run the CTR encryption or decryption. + * + * The `iv` parameter points to the IV for this run; its + * length is exactly 4 bytes less than the block size (e.g. + * 12 bytes for AES/CTR). The IV is combined with a 32-bit + * block counter to produce the block value which is processed + * with the block cipher. + * + * The data to encrypt or decrypt is updated "in place". Its + * length (`len` bytes) is not required to be a multiple of + * the block size; if the final block is partial, then the + * corresponding key stream bits are dropped. + * + * The resulting counter value is returned. + * + * \param ctx context structure (already initialised). + * \param iv IV for CTR encryption/decryption. + * \param cc initial value for the block counter. + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + * \return the new block counter value. + */ + uint32_t (*run)(const br_block_ctr_class *const *ctx, + const void *iv, uint32_t cc, void *data, size_t len); +}; + +/** + * \brief Class type for combined CTR and CBC-MAC implementations. + * + * A `br_block_ctrcbc_class` instance points to the functions implementing + * a specific block cipher, when used in CTR mode for encrypting or + * decrypting data, along with CBC-MAC. + */ +typedef struct br_block_ctrcbc_class_ br_block_ctrcbc_class; +struct br_block_ctrcbc_class_ { + /** + * \brief Size (in bytes) of the context structure appropriate + * for containing subkeys. + */ + size_t context_size; + + /** + * \brief Size of individual blocks (in bytes). + */ + unsigned block_size; + + /** + * \brief Base-2 logarithm of the size of individual blocks, + * expressed in bytes. + */ + unsigned log_block_size; + + /** + * \brief Initialisation function. + * + * This function sets the `vtable` field in the context structure. + * The key length MUST be one of the key lengths supported by + * the implementation. + * + * \param ctx context structure to initialise. + * \param key secret key. + * \param key_len key length (in bytes). + */ + void (*init)(const br_block_ctrcbc_class **ctx, + const void *key, size_t key_len); + + /** + * \brief Run the CTR encryption + CBC-MAC. + * + * The `ctr` parameter points to the counter; its length shall + * be equal to the block size. It is updated by this function + * as encryption proceeds. + * + * The `cbcmac` parameter points to the IV for CBC-MAC. The MAC + * is computed over the encrypted data (output of CTR + * encryption). Its length shall be equal to the block size. The + * computed CBC-MAC value is written over the `cbcmac` array. + * + * The data to encrypt is updated "in place". Its length (`len` + * bytes) MUST be a multiple of the block size. + * + * \param ctx context structure (already initialised). + * \param ctr counter for CTR encryption (initial and final). + * \param cbcmac IV and output buffer for CBC-MAC. + * \param data data to encrypt. + * \param len data length (in bytes). + */ + void (*encrypt)(const br_block_ctrcbc_class *const *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + + /** + * \brief Run the CTR decryption + CBC-MAC. + * + * The `ctr` parameter points to the counter; its length shall + * be equal to the block size. It is updated by this function + * as decryption proceeds. + * + * The `cbcmac` parameter points to the IV for CBC-MAC. The MAC + * is computed over the encrypted data (i.e. before CTR + * decryption). Its length shall be equal to the block size. The + * computed CBC-MAC value is written over the `cbcmac` array. + * + * The data to decrypt is updated "in place". Its length (`len` + * bytes) MUST be a multiple of the block size. + * + * \param ctx context structure (already initialised). + * \param ctr counter for CTR encryption (initial and final). + * \param cbcmac IV and output buffer for CBC-MAC. + * \param data data to decrypt. + * \param len data length (in bytes). + */ + void (*decrypt)(const br_block_ctrcbc_class *const *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + + /** + * \brief Run the CTR encryption/decryption only. + * + * The `ctr` parameter points to the counter; its length shall + * be equal to the block size. It is updated by this function + * as decryption proceeds. + * + * The data to decrypt is updated "in place". Its length (`len` + * bytes) MUST be a multiple of the block size. + * + * \param ctx context structure (already initialised). + * \param ctr counter for CTR encryption (initial and final). + * \param data data to decrypt. + * \param len data length (in bytes). + */ + void (*ctr)(const br_block_ctrcbc_class *const *ctx, + void *ctr, void *data, size_t len); + + /** + * \brief Run the CBC-MAC only. + * + * The `cbcmac` parameter points to the IV for CBC-MAC. The MAC + * is computed over the encrypted data (i.e. before CTR + * decryption). Its length shall be equal to the block size. The + * computed CBC-MAC value is written over the `cbcmac` array. + * + * The data is unmodified. Its length (`len` bytes) MUST be a + * multiple of the block size. + * + * \param ctx context structure (already initialised). + * \param cbcmac IV and output buffer for CBC-MAC. + * \param data data to decrypt. + * \param len data length (in bytes). + */ + void (*mac)(const br_block_ctrcbc_class *const *ctx, + void *cbcmac, const void *data, size_t len); +}; + +/* + * Traditional, table-based AES implementation. It is fast, but uses + * internal tables (in particular a 1 kB table for encryption, another + * 1 kB table for decryption, and a 256-byte table for key schedule), + * and it is not constant-time. In contexts where cache-timing attacks + * apply, this implementation may leak the secret key. + */ + +/** \brief AES block size (16 bytes). */ +#define br_aes_big_BLOCK_SIZE 16 + +/** + * \brief Context for AES subkeys (`aes_big` implementation, CBC encryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcenc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[60]; + unsigned num_rounds; +#endif +} br_aes_big_cbcenc_keys; + +/** + * \brief Context for AES subkeys (`aes_big` implementation, CBC decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcdec_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[60]; + unsigned num_rounds; +#endif +} br_aes_big_cbcdec_keys; + +/** + * \brief Context for AES subkeys (`aes_big` implementation, CTR encryption + * and decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_ctr_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[60]; + unsigned num_rounds; +#endif +} br_aes_big_ctr_keys; + +/** + * \brief Context for AES subkeys (`aes_big` implementation, CTR encryption + * and decryption + CBC-MAC). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_ctrcbc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[60]; + unsigned num_rounds; +#endif +} br_aes_big_ctrcbc_keys; + +/** + * \brief Class instance for AES CBC encryption (`aes_big` implementation). + */ +extern const br_block_cbcenc_class br_aes_big_cbcenc_vtable; + +/** + * \brief Class instance for AES CBC decryption (`aes_big` implementation). + */ +extern const br_block_cbcdec_class br_aes_big_cbcdec_vtable; + +/** + * \brief Class instance for AES CTR encryption and decryption + * (`aes_big` implementation). + */ +extern const br_block_ctr_class br_aes_big_ctr_vtable; + +/** + * \brief Class instance for AES CTR encryption/decryption + CBC-MAC + * (`aes_big` implementation). + */ +extern const br_block_ctrcbc_class br_aes_big_ctrcbc_vtable; + +/** + * \brief Context initialisation (key schedule) for AES CBC encryption + * (`aes_big` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_big_cbcenc_init(br_aes_big_cbcenc_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CBC decryption + * (`aes_big` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_big_cbcdec_init(br_aes_big_cbcdec_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CTR encryption + * and decryption (`aes_big` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_big_ctr_init(br_aes_big_ctr_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CTR + CBC-MAC + * (`aes_big` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_big_ctrcbc_init(br_aes_big_ctrcbc_keys *ctx, + const void *key, size_t len); + +/** + * \brief CBC encryption with AES (`aes_big` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be multiple of 16). + */ +void br_aes_big_cbcenc_run(const br_aes_big_cbcenc_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CBC decryption with AES (`aes_big` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be multiple of 16). + */ +void br_aes_big_cbcdec_run(const br_aes_big_cbcdec_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CTR encryption and decryption with AES (`aes_big` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (constant, 12 bytes). + * \param cc initial block counter value. + * \param data data to encrypt or decrypt (updated). + * \param len data length (in bytes). + * \return new block counter value. + */ +uint32_t br_aes_big_ctr_run(const br_aes_big_ctr_keys *ctx, + const void *iv, uint32_t cc, void *data, size_t len); + +/** + * \brief CTR encryption + CBC-MAC with AES (`aes_big` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_big_ctrcbc_encrypt(const br_aes_big_ctrcbc_keys *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + +/** + * \brief CTR decryption + CBC-MAC with AES (`aes_big` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_big_ctrcbc_decrypt(const br_aes_big_ctrcbc_keys *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + +/** + * \brief CTR encryption/decryption with AES (`aes_big` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param data data to MAC (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_big_ctrcbc_ctr(const br_aes_big_ctrcbc_keys *ctx, + void *ctr, void *data, size_t len); + +/** + * \brief CBC-MAC with AES (`aes_big` implementation). + * + * \param ctx context (already initialised). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to MAC (unmodified). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_big_ctrcbc_mac(const br_aes_big_ctrcbc_keys *ctx, + void *cbcmac, const void *data, size_t len); + +/* + * AES implementation optimized for size. It is slower than the + * traditional table-based AES implementation, but requires much less + * code. It still uses data-dependent table accesses (albeit within a + * much smaller 256-byte table), which makes it conceptually vulnerable + * to cache-timing attacks. + */ + +/** \brief AES block size (16 bytes). */ +#define br_aes_small_BLOCK_SIZE 16 + +/** + * \brief Context for AES subkeys (`aes_small` implementation, CBC encryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcenc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[60]; + unsigned num_rounds; +#endif +} br_aes_small_cbcenc_keys; + +/** + * \brief Context for AES subkeys (`aes_small` implementation, CBC decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcdec_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[60]; + unsigned num_rounds; +#endif +} br_aes_small_cbcdec_keys; + +/** + * \brief Context for AES subkeys (`aes_small` implementation, CTR encryption + * and decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_ctr_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[60]; + unsigned num_rounds; +#endif +} br_aes_small_ctr_keys; + +/** + * \brief Context for AES subkeys (`aes_small` implementation, CTR encryption + * and decryption + CBC-MAC). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_ctrcbc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[60]; + unsigned num_rounds; +#endif +} br_aes_small_ctrcbc_keys; + +/** + * \brief Class instance for AES CBC encryption (`aes_small` implementation). + */ +extern const br_block_cbcenc_class br_aes_small_cbcenc_vtable; + +/** + * \brief Class instance for AES CBC decryption (`aes_small` implementation). + */ +extern const br_block_cbcdec_class br_aes_small_cbcdec_vtable; + +/** + * \brief Class instance for AES CTR encryption and decryption + * (`aes_small` implementation). + */ +extern const br_block_ctr_class br_aes_small_ctr_vtable; + +/** + * \brief Class instance for AES CTR encryption/decryption + CBC-MAC + * (`aes_small` implementation). + */ +extern const br_block_ctrcbc_class br_aes_small_ctrcbc_vtable; + +/** + * \brief Context initialisation (key schedule) for AES CBC encryption + * (`aes_small` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_small_cbcenc_init(br_aes_small_cbcenc_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CBC decryption + * (`aes_small` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_small_cbcdec_init(br_aes_small_cbcdec_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CTR encryption + * and decryption (`aes_small` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_small_ctr_init(br_aes_small_ctr_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CTR + CBC-MAC + * (`aes_small` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_small_ctrcbc_init(br_aes_small_ctrcbc_keys *ctx, + const void *key, size_t len); + +/** + * \brief CBC encryption with AES (`aes_small` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be multiple of 16). + */ +void br_aes_small_cbcenc_run(const br_aes_small_cbcenc_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CBC decryption with AES (`aes_small` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be multiple of 16). + */ +void br_aes_small_cbcdec_run(const br_aes_small_cbcdec_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CTR encryption and decryption with AES (`aes_small` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (constant, 12 bytes). + * \param cc initial block counter value. + * \param data data to decrypt (updated). + * \param len data length (in bytes). + * \return new block counter value. + */ +uint32_t br_aes_small_ctr_run(const br_aes_small_ctr_keys *ctx, + const void *iv, uint32_t cc, void *data, size_t len); + +/** + * \brief CTR encryption + CBC-MAC with AES (`aes_small` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_small_ctrcbc_encrypt(const br_aes_small_ctrcbc_keys *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + +/** + * \brief CTR decryption + CBC-MAC with AES (`aes_small` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_small_ctrcbc_decrypt(const br_aes_small_ctrcbc_keys *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + +/** + * \brief CTR encryption/decryption with AES (`aes_small` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param data data to MAC (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_small_ctrcbc_ctr(const br_aes_small_ctrcbc_keys *ctx, + void *ctr, void *data, size_t len); + +/** + * \brief CBC-MAC with AES (`aes_small` implementation). + * + * \param ctx context (already initialised). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to MAC (unmodified). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_small_ctrcbc_mac(const br_aes_small_ctrcbc_keys *ctx, + void *cbcmac, const void *data, size_t len); + +/* + * Constant-time AES implementation. Its size is similar to that of + * 'aes_big', and its performance is similar to that of 'aes_small' (faster + * decryption, slower encryption). However, it is constant-time, i.e. + * immune to cache-timing and similar attacks. + */ + +/** \brief AES block size (16 bytes). */ +#define br_aes_ct_BLOCK_SIZE 16 + +/** + * \brief Context for AES subkeys (`aes_ct` implementation, CBC encryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcenc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[60]; + unsigned num_rounds; +#endif +} br_aes_ct_cbcenc_keys; + +/** + * \brief Context for AES subkeys (`aes_ct` implementation, CBC decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcdec_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[60]; + unsigned num_rounds; +#endif +} br_aes_ct_cbcdec_keys; + +/** + * \brief Context for AES subkeys (`aes_ct` implementation, CTR encryption + * and decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_ctr_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[60]; + unsigned num_rounds; +#endif +} br_aes_ct_ctr_keys; + +/** + * \brief Context for AES subkeys (`aes_ct` implementation, CTR encryption + * and decryption + CBC-MAC). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_ctrcbc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[60]; + unsigned num_rounds; +#endif +} br_aes_ct_ctrcbc_keys; + +/** + * \brief Class instance for AES CBC encryption (`aes_ct` implementation). + */ +extern const br_block_cbcenc_class br_aes_ct_cbcenc_vtable; + +/** + * \brief Class instance for AES CBC decryption (`aes_ct` implementation). + */ +extern const br_block_cbcdec_class br_aes_ct_cbcdec_vtable; + +/** + * \brief Class instance for AES CTR encryption and decryption + * (`aes_ct` implementation). + */ +extern const br_block_ctr_class br_aes_ct_ctr_vtable; + +/** + * \brief Class instance for AES CTR encryption/decryption + CBC-MAC + * (`aes_ct` implementation). + */ +extern const br_block_ctrcbc_class br_aes_ct_ctrcbc_vtable; + +/** + * \brief Context initialisation (key schedule) for AES CBC encryption + * (`aes_ct` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_ct_cbcenc_init(br_aes_ct_cbcenc_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CBC decryption + * (`aes_ct` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_ct_cbcdec_init(br_aes_ct_cbcdec_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CTR encryption + * and decryption (`aes_ct` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_ct_ctr_init(br_aes_ct_ctr_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CTR + CBC-MAC + * (`aes_ct` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_ct_ctrcbc_init(br_aes_ct_ctrcbc_keys *ctx, + const void *key, size_t len); + +/** + * \brief CBC encryption with AES (`aes_ct` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be multiple of 16). + */ +void br_aes_ct_cbcenc_run(const br_aes_ct_cbcenc_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CBC decryption with AES (`aes_ct` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be multiple of 16). + */ +void br_aes_ct_cbcdec_run(const br_aes_ct_cbcdec_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CTR encryption and decryption with AES (`aes_ct` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (constant, 12 bytes). + * \param cc initial block counter value. + * \param data data to decrypt (updated). + * \param len data length (in bytes). + * \return new block counter value. + */ +uint32_t br_aes_ct_ctr_run(const br_aes_ct_ctr_keys *ctx, + const void *iv, uint32_t cc, void *data, size_t len); + +/** + * \brief CTR encryption + CBC-MAC with AES (`aes_ct` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_ct_ctrcbc_encrypt(const br_aes_ct_ctrcbc_keys *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + +/** + * \brief CTR decryption + CBC-MAC with AES (`aes_ct` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_ct_ctrcbc_decrypt(const br_aes_ct_ctrcbc_keys *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + +/** + * \brief CTR encryption/decryption with AES (`aes_ct` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param data data to MAC (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_ct_ctrcbc_ctr(const br_aes_ct_ctrcbc_keys *ctx, + void *ctr, void *data, size_t len); + +/** + * \brief CBC-MAC with AES (`aes_ct` implementation). + * + * \param ctx context (already initialised). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to MAC (unmodified). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_ct_ctrcbc_mac(const br_aes_ct_ctrcbc_keys *ctx, + void *cbcmac, const void *data, size_t len); + +/* + * 64-bit constant-time AES implementation. It is similar to 'aes_ct' + * but uses 64-bit registers, making it about twice faster than 'aes_ct' + * on 64-bit platforms, while remaining constant-time and with a similar + * code size. (The doubling in performance is only for CBC decryption + * and CTR mode; CBC encryption is non-parallel and cannot benefit from + * the larger registers.) + */ + +/** \brief AES block size (16 bytes). */ +#define br_aes_ct64_BLOCK_SIZE 16 + +/** + * \brief Context for AES subkeys (`aes_ct64` implementation, CBC encryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcenc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint64_t skey[30]; + unsigned num_rounds; +#endif +} br_aes_ct64_cbcenc_keys; + +/** + * \brief Context for AES subkeys (`aes_ct64` implementation, CBC decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcdec_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint64_t skey[30]; + unsigned num_rounds; +#endif +} br_aes_ct64_cbcdec_keys; + +/** + * \brief Context for AES subkeys (`aes_ct64` implementation, CTR encryption + * and decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_ctr_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint64_t skey[30]; + unsigned num_rounds; +#endif +} br_aes_ct64_ctr_keys; + +/** + * \brief Context for AES subkeys (`aes_ct64` implementation, CTR encryption + * and decryption + CBC-MAC). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_ctrcbc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint64_t skey[30]; + unsigned num_rounds; +#endif +} br_aes_ct64_ctrcbc_keys; + +/** + * \brief Class instance for AES CBC encryption (`aes_ct64` implementation). + */ +extern const br_block_cbcenc_class br_aes_ct64_cbcenc_vtable; + +/** + * \brief Class instance for AES CBC decryption (`aes_ct64` implementation). + */ +extern const br_block_cbcdec_class br_aes_ct64_cbcdec_vtable; + +/** + * \brief Class instance for AES CTR encryption and decryption + * (`aes_ct64` implementation). + */ +extern const br_block_ctr_class br_aes_ct64_ctr_vtable; + +/** + * \brief Class instance for AES CTR encryption/decryption + CBC-MAC + * (`aes_ct64` implementation). + */ +extern const br_block_ctrcbc_class br_aes_ct64_ctrcbc_vtable; + +/** + * \brief Context initialisation (key schedule) for AES CBC encryption + * (`aes_ct64` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_ct64_cbcenc_init(br_aes_ct64_cbcenc_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CBC decryption + * (`aes_ct64` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_ct64_cbcdec_init(br_aes_ct64_cbcdec_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CTR encryption + * and decryption (`aes_ct64` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_ct64_ctr_init(br_aes_ct64_ctr_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CTR + CBC-MAC + * (`aes_ct64` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_ct64_ctrcbc_init(br_aes_ct64_ctrcbc_keys *ctx, + const void *key, size_t len); + +/** + * \brief CBC encryption with AES (`aes_ct64` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be multiple of 16). + */ +void br_aes_ct64_cbcenc_run(const br_aes_ct64_cbcenc_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CBC decryption with AES (`aes_ct64` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be multiple of 16). + */ +void br_aes_ct64_cbcdec_run(const br_aes_ct64_cbcdec_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CTR encryption and decryption with AES (`aes_ct64` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (constant, 12 bytes). + * \param cc initial block counter value. + * \param data data to decrypt (updated). + * \param len data length (in bytes). + * \return new block counter value. + */ +uint32_t br_aes_ct64_ctr_run(const br_aes_ct64_ctr_keys *ctx, + const void *iv, uint32_t cc, void *data, size_t len); + +/** + * \brief CTR encryption + CBC-MAC with AES (`aes_ct64` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_ct64_ctrcbc_encrypt(const br_aes_ct64_ctrcbc_keys *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + +/** + * \brief CTR decryption + CBC-MAC with AES (`aes_ct64` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_ct64_ctrcbc_decrypt(const br_aes_ct64_ctrcbc_keys *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + +/** + * \brief CTR encryption/decryption with AES (`aes_ct64` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param data data to MAC (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_ct64_ctrcbc_ctr(const br_aes_ct64_ctrcbc_keys *ctx, + void *ctr, void *data, size_t len); + +/** + * \brief CBC-MAC with AES (`aes_ct64` implementation). + * + * \param ctx context (already initialised). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to MAC (unmodified). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_ct64_ctrcbc_mac(const br_aes_ct64_ctrcbc_keys *ctx, + void *cbcmac, const void *data, size_t len); + +/* + * AES implementation using AES-NI opcodes (x86 platform). + */ + +/** \brief AES block size (16 bytes). */ +#define br_aes_x86ni_BLOCK_SIZE 16 + +/** + * \brief Context for AES subkeys (`aes_x86ni` implementation, CBC encryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcenc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + union { + unsigned char skni[16 * 15]; + } skey; + unsigned num_rounds; +#endif +} br_aes_x86ni_cbcenc_keys; + +/** + * \brief Context for AES subkeys (`aes_x86ni` implementation, CBC decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcdec_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + union { + unsigned char skni[16 * 15]; + } skey; + unsigned num_rounds; +#endif +} br_aes_x86ni_cbcdec_keys; + +/** + * \brief Context for AES subkeys (`aes_x86ni` implementation, CTR encryption + * and decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_ctr_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + union { + unsigned char skni[16 * 15]; + } skey; + unsigned num_rounds; +#endif +} br_aes_x86ni_ctr_keys; + +/** + * \brief Context for AES subkeys (`aes_x86ni` implementation, CTR encryption + * and decryption + CBC-MAC). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_ctrcbc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + union { + unsigned char skni[16 * 15]; + } skey; + unsigned num_rounds; +#endif +} br_aes_x86ni_ctrcbc_keys; + +/** + * \brief Class instance for AES CBC encryption (`aes_x86ni` implementation). + * + * Since this implementation might be omitted from the library, or the + * AES opcode unavailable on the current CPU, a pointer to this class + * instance should be obtained through `br_aes_x86ni_cbcenc_get_vtable()`. + */ +extern const br_block_cbcenc_class br_aes_x86ni_cbcenc_vtable; + +/** + * \brief Class instance for AES CBC decryption (`aes_x86ni` implementation). + * + * Since this implementation might be omitted from the library, or the + * AES opcode unavailable on the current CPU, a pointer to this class + * instance should be obtained through `br_aes_x86ni_cbcdec_get_vtable()`. + */ +extern const br_block_cbcdec_class br_aes_x86ni_cbcdec_vtable; + +/** + * \brief Class instance for AES CTR encryption and decryption + * (`aes_x86ni` implementation). + * + * Since this implementation might be omitted from the library, or the + * AES opcode unavailable on the current CPU, a pointer to this class + * instance should be obtained through `br_aes_x86ni_ctr_get_vtable()`. + */ +extern const br_block_ctr_class br_aes_x86ni_ctr_vtable; + +/** + * \brief Class instance for AES CTR encryption/decryption + CBC-MAC + * (`aes_x86ni` implementation). + * + * Since this implementation might be omitted from the library, or the + * AES opcode unavailable on the current CPU, a pointer to this class + * instance should be obtained through `br_aes_x86ni_ctrcbc_get_vtable()`. + */ +extern const br_block_ctrcbc_class br_aes_x86ni_ctrcbc_vtable; + +/** + * \brief Context initialisation (key schedule) for AES CBC encryption + * (`aes_x86ni` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_x86ni_cbcenc_init(br_aes_x86ni_cbcenc_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CBC decryption + * (`aes_x86ni` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_x86ni_cbcdec_init(br_aes_x86ni_cbcdec_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CTR encryption + * and decryption (`aes_x86ni` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_x86ni_ctr_init(br_aes_x86ni_ctr_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CTR + CBC-MAC + * (`aes_x86ni` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_x86ni_ctrcbc_init(br_aes_x86ni_ctrcbc_keys *ctx, + const void *key, size_t len); + +/** + * \brief CBC encryption with AES (`aes_x86ni` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be multiple of 16). + */ +void br_aes_x86ni_cbcenc_run(const br_aes_x86ni_cbcenc_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CBC decryption with AES (`aes_x86ni` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be multiple of 16). + */ +void br_aes_x86ni_cbcdec_run(const br_aes_x86ni_cbcdec_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CTR encryption and decryption with AES (`aes_x86ni` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (constant, 12 bytes). + * \param cc initial block counter value. + * \param data data to decrypt (updated). + * \param len data length (in bytes). + * \return new block counter value. + */ +uint32_t br_aes_x86ni_ctr_run(const br_aes_x86ni_ctr_keys *ctx, + const void *iv, uint32_t cc, void *data, size_t len); + +/** + * \brief CTR encryption + CBC-MAC with AES (`aes_x86ni` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_x86ni_ctrcbc_encrypt(const br_aes_x86ni_ctrcbc_keys *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + +/** + * \brief CTR decryption + CBC-MAC with AES (`aes_x86ni` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_x86ni_ctrcbc_decrypt(const br_aes_x86ni_ctrcbc_keys *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + +/** + * \brief CTR encryption/decryption with AES (`aes_x86ni` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param data data to MAC (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_x86ni_ctrcbc_ctr(const br_aes_x86ni_ctrcbc_keys *ctx, + void *ctr, void *data, size_t len); + +/** + * \brief CBC-MAC with AES (`aes_x86ni` implementation). + * + * \param ctx context (already initialised). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to MAC (unmodified). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_x86ni_ctrcbc_mac(const br_aes_x86ni_ctrcbc_keys *ctx, + void *cbcmac, const void *data, size_t len); + +/** + * \brief Obtain the `aes_x86ni` AES-CBC (encryption) implementation, if + * available. + * + * This function returns a pointer to `br_aes_x86ni_cbcenc_vtable`, if + * that implementation was compiled in the library _and_ the x86 AES + * opcodes are available on the currently running CPU. If either of + * these conditions is not met, then this function returns `NULL`. + * + * \return the `aes_x86ni` AES-CBC (encryption) implementation, or `NULL`. + */ +const br_block_cbcenc_class *br_aes_x86ni_cbcenc_get_vtable(void); + +/** + * \brief Obtain the `aes_x86ni` AES-CBC (decryption) implementation, if + * available. + * + * This function returns a pointer to `br_aes_x86ni_cbcdec_vtable`, if + * that implementation was compiled in the library _and_ the x86 AES + * opcodes are available on the currently running CPU. If either of + * these conditions is not met, then this function returns `NULL`. + * + * \return the `aes_x86ni` AES-CBC (decryption) implementation, or `NULL`. + */ +const br_block_cbcdec_class *br_aes_x86ni_cbcdec_get_vtable(void); + +/** + * \brief Obtain the `aes_x86ni` AES-CTR implementation, if available. + * + * This function returns a pointer to `br_aes_x86ni_ctr_vtable`, if + * that implementation was compiled in the library _and_ the x86 AES + * opcodes are available on the currently running CPU. If either of + * these conditions is not met, then this function returns `NULL`. + * + * \return the `aes_x86ni` AES-CTR implementation, or `NULL`. + */ +const br_block_ctr_class *br_aes_x86ni_ctr_get_vtable(void); + +/** + * \brief Obtain the `aes_x86ni` AES-CTR + CBC-MAC implementation, if + * available. + * + * This function returns a pointer to `br_aes_x86ni_ctrcbc_vtable`, if + * that implementation was compiled in the library _and_ the x86 AES + * opcodes are available on the currently running CPU. If either of + * these conditions is not met, then this function returns `NULL`. + * + * \return the `aes_x86ni` AES-CTR implementation, or `NULL`. + */ +const br_block_ctrcbc_class *br_aes_x86ni_ctrcbc_get_vtable(void); + +/* + * AES implementation using POWER8 opcodes. + */ + +/** \brief AES block size (16 bytes). */ +#define br_aes_pwr8_BLOCK_SIZE 16 + +/** + * \brief Context for AES subkeys (`aes_pwr8` implementation, CBC encryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcenc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + union { + unsigned char skni[16 * 15]; + } skey; + unsigned num_rounds; +#endif +} br_aes_pwr8_cbcenc_keys; + +/** + * \brief Context for AES subkeys (`aes_pwr8` implementation, CBC decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcdec_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + union { + unsigned char skni[16 * 15]; + } skey; + unsigned num_rounds; +#endif +} br_aes_pwr8_cbcdec_keys; + +/** + * \brief Context for AES subkeys (`aes_pwr8` implementation, CTR encryption + * and decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_ctr_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + union { + unsigned char skni[16 * 15]; + } skey; + unsigned num_rounds; +#endif +} br_aes_pwr8_ctr_keys; + +/** + * \brief Context for AES subkeys (`aes_pwr8` implementation, CTR encryption + * and decryption + CBC-MAC). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_ctrcbc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + union { + unsigned char skni[16 * 15]; + } skey; + unsigned num_rounds; +#endif +} br_aes_pwr8_ctrcbc_keys; + +/** + * \brief Class instance for AES CBC encryption (`aes_pwr8` implementation). + * + * Since this implementation might be omitted from the library, or the + * AES opcode unavailable on the current CPU, a pointer to this class + * instance should be obtained through `br_aes_pwr8_cbcenc_get_vtable()`. + */ +extern const br_block_cbcenc_class br_aes_pwr8_cbcenc_vtable; + +/** + * \brief Class instance for AES CBC decryption (`aes_pwr8` implementation). + * + * Since this implementation might be omitted from the library, or the + * AES opcode unavailable on the current CPU, a pointer to this class + * instance should be obtained through `br_aes_pwr8_cbcdec_get_vtable()`. + */ +extern const br_block_cbcdec_class br_aes_pwr8_cbcdec_vtable; + +/** + * \brief Class instance for AES CTR encryption and decryption + * (`aes_pwr8` implementation). + * + * Since this implementation might be omitted from the library, or the + * AES opcode unavailable on the current CPU, a pointer to this class + * instance should be obtained through `br_aes_pwr8_ctr_get_vtable()`. + */ +extern const br_block_ctr_class br_aes_pwr8_ctr_vtable; + +/** + * \brief Class instance for AES CTR encryption/decryption + CBC-MAC + * (`aes_pwr8` implementation). + * + * Since this implementation might be omitted from the library, or the + * AES opcode unavailable on the current CPU, a pointer to this class + * instance should be obtained through `br_aes_pwr8_ctrcbc_get_vtable()`. + */ +extern const br_block_ctrcbc_class br_aes_pwr8_ctrcbc_vtable; + +/** + * \brief Context initialisation (key schedule) for AES CBC encryption + * (`aes_pwr8` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_pwr8_cbcenc_init(br_aes_pwr8_cbcenc_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CBC decryption + * (`aes_pwr8` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_pwr8_cbcdec_init(br_aes_pwr8_cbcdec_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CTR encryption + * and decryption (`aes_pwr8` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_pwr8_ctr_init(br_aes_pwr8_ctr_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for AES CTR + CBC-MAC + * (`aes_pwr8` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_aes_pwr8_ctrcbc_init(br_aes_pwr8_ctrcbc_keys *ctx, + const void *key, size_t len); + +/** + * \brief CBC encryption with AES (`aes_pwr8` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be multiple of 16). + */ +void br_aes_pwr8_cbcenc_run(const br_aes_pwr8_cbcenc_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CBC decryption with AES (`aes_pwr8` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be multiple of 16). + */ +void br_aes_pwr8_cbcdec_run(const br_aes_pwr8_cbcdec_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CTR encryption and decryption with AES (`aes_pwr8` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (constant, 12 bytes). + * \param cc initial block counter value. + * \param data data to decrypt (updated). + * \param len data length (in bytes). + * \return new block counter value. + */ +uint32_t br_aes_pwr8_ctr_run(const br_aes_pwr8_ctr_keys *ctx, + const void *iv, uint32_t cc, void *data, size_t len); + +/** + * \brief CTR encryption + CBC-MAC with AES (`aes_pwr8` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_pwr8_ctrcbc_encrypt(const br_aes_pwr8_ctrcbc_keys *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + +/** + * \brief CTR decryption + CBC-MAC with AES (`aes_pwr8` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_pwr8_ctrcbc_decrypt(const br_aes_pwr8_ctrcbc_keys *ctx, + void *ctr, void *cbcmac, void *data, size_t len); + +/** + * \brief CTR encryption/decryption with AES (`aes_pwr8` implementation). + * + * \param ctx context (already initialised). + * \param ctr counter for CTR (16 bytes, updated). + * \param data data to MAC (updated). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_pwr8_ctrcbc_ctr(const br_aes_pwr8_ctrcbc_keys *ctx, + void *ctr, void *data, size_t len); + +/** + * \brief CBC-MAC with AES (`aes_pwr8` implementation). + * + * \param ctx context (already initialised). + * \param cbcmac IV for CBC-MAC (updated). + * \param data data to MAC (unmodified). + * \param len data length (in bytes, MUST be a multiple of 16). + */ +void br_aes_pwr8_ctrcbc_mac(const br_aes_pwr8_ctrcbc_keys *ctx, + void *cbcmac, const void *data, size_t len); + +/** + * \brief Obtain the `aes_pwr8` AES-CBC (encryption) implementation, if + * available. + * + * This function returns a pointer to `br_aes_pwr8_cbcenc_vtable`, if + * that implementation was compiled in the library _and_ the POWER8 + * crypto opcodes are available on the currently running CPU. If either + * of these conditions is not met, then this function returns `NULL`. + * + * \return the `aes_pwr8` AES-CBC (encryption) implementation, or `NULL`. + */ +const br_block_cbcenc_class *br_aes_pwr8_cbcenc_get_vtable(void); + +/** + * \brief Obtain the `aes_pwr8` AES-CBC (decryption) implementation, if + * available. + * + * This function returns a pointer to `br_aes_pwr8_cbcdec_vtable`, if + * that implementation was compiled in the library _and_ the POWER8 + * crypto opcodes are available on the currently running CPU. If either + * of these conditions is not met, then this function returns `NULL`. + * + * \return the `aes_pwr8` AES-CBC (decryption) implementation, or `NULL`. + */ +const br_block_cbcdec_class *br_aes_pwr8_cbcdec_get_vtable(void); + +/** + * \brief Obtain the `aes_pwr8` AES-CTR implementation, if available. + * + * This function returns a pointer to `br_aes_pwr8_ctr_vtable`, if that + * implementation was compiled in the library _and_ the POWER8 crypto + * opcodes are available on the currently running CPU. If either of + * these conditions is not met, then this function returns `NULL`. + * + * \return the `aes_pwr8` AES-CTR implementation, or `NULL`. + */ +const br_block_ctr_class *br_aes_pwr8_ctr_get_vtable(void); + +/** + * \brief Obtain the `aes_pwr8` AES-CTR + CBC-MAC implementation, if + * available. + * + * This function returns a pointer to `br_aes_pwr8_ctrcbc_vtable`, if + * that implementation was compiled in the library _and_ the POWER8 AES + * opcodes are available on the currently running CPU. If either of + * these conditions is not met, then this function returns `NULL`. + * + * \return the `aes_pwr8` AES-CTR implementation, or `NULL`. + */ +const br_block_ctrcbc_class *br_aes_pwr8_ctrcbc_get_vtable(void); + +/** + * \brief Aggregate structure large enough to be used as context for + * subkeys (CBC encryption) for all AES implementations. + */ +typedef union { + const br_block_cbcenc_class *vtable; + br_aes_big_cbcenc_keys c_big; + br_aes_small_cbcenc_keys c_small; + br_aes_ct_cbcenc_keys c_ct; + br_aes_ct64_cbcenc_keys c_ct64; + br_aes_x86ni_cbcenc_keys c_x86ni; + br_aes_pwr8_cbcenc_keys c_pwr8; +} br_aes_gen_cbcenc_keys; + +/** + * \brief Aggregate structure large enough to be used as context for + * subkeys (CBC decryption) for all AES implementations. + */ +typedef union { + const br_block_cbcdec_class *vtable; + br_aes_big_cbcdec_keys c_big; + br_aes_small_cbcdec_keys c_small; + br_aes_ct_cbcdec_keys c_ct; + br_aes_ct64_cbcdec_keys c_ct64; + br_aes_x86ni_cbcdec_keys c_x86ni; + br_aes_pwr8_cbcdec_keys c_pwr8; +} br_aes_gen_cbcdec_keys; + +/** + * \brief Aggregate structure large enough to be used as context for + * subkeys (CTR encryption and decryption) for all AES implementations. + */ +typedef union { + const br_block_ctr_class *vtable; + br_aes_big_ctr_keys c_big; + br_aes_small_ctr_keys c_small; + br_aes_ct_ctr_keys c_ct; + br_aes_ct64_ctr_keys c_ct64; + br_aes_x86ni_ctr_keys c_x86ni; + br_aes_pwr8_ctr_keys c_pwr8; +} br_aes_gen_ctr_keys; + +/** + * \brief Aggregate structure large enough to be used as context for + * subkeys (CTR encryption/decryption + CBC-MAC) for all AES implementations. + */ +typedef union { + const br_block_ctrcbc_class *vtable; + br_aes_big_ctrcbc_keys c_big; + br_aes_small_ctrcbc_keys c_small; + br_aes_ct_ctrcbc_keys c_ct; + br_aes_ct64_ctrcbc_keys c_ct64; + br_aes_x86ni_ctrcbc_keys c_x86ni; + br_aes_pwr8_ctrcbc_keys c_pwr8; +} br_aes_gen_ctrcbc_keys; + +/* + * Traditional, table-based implementation for DES/3DES. Since tables are + * used, cache-timing attacks are conceptually possible. + */ + +/** \brief DES/3DES block size (8 bytes). */ +#define br_des_tab_BLOCK_SIZE 8 + +/** + * \brief Context for DES subkeys (`des_tab` implementation, CBC encryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcenc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[96]; + unsigned num_rounds; +#endif +} br_des_tab_cbcenc_keys; + +/** + * \brief Context for DES subkeys (`des_tab` implementation, CBC decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcdec_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[96]; + unsigned num_rounds; +#endif +} br_des_tab_cbcdec_keys; + +/** + * \brief Class instance for DES CBC encryption (`des_tab` implementation). + */ +extern const br_block_cbcenc_class br_des_tab_cbcenc_vtable; + +/** + * \brief Class instance for DES CBC decryption (`des_tab` implementation). + */ +extern const br_block_cbcdec_class br_des_tab_cbcdec_vtable; + +/** + * \brief Context initialisation (key schedule) for DES CBC encryption + * (`des_tab` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_des_tab_cbcenc_init(br_des_tab_cbcenc_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for DES CBC decryption + * (`des_tab` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_des_tab_cbcdec_init(br_des_tab_cbcdec_keys *ctx, + const void *key, size_t len); + +/** + * \brief CBC encryption with DES (`des_tab` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be multiple of 8). + */ +void br_des_tab_cbcenc_run(const br_des_tab_cbcenc_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CBC decryption with DES (`des_tab` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be multiple of 8). + */ +void br_des_tab_cbcdec_run(const br_des_tab_cbcdec_keys *ctx, void *iv, + void *data, size_t len); + +/* + * Constant-time implementation for DES/3DES. It is substantially slower + * (by a factor of about 4x), but also immune to cache-timing attacks. + */ + +/** \brief DES/3DES block size (8 bytes). */ +#define br_des_ct_BLOCK_SIZE 8 + +/** + * \brief Context for DES subkeys (`des_ct` implementation, CBC encryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcenc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[96]; + unsigned num_rounds; +#endif +} br_des_ct_cbcenc_keys; + +/** + * \brief Context for DES subkeys (`des_ct` implementation, CBC decryption). + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** \brief Pointer to vtable for this context. */ + const br_block_cbcdec_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint32_t skey[96]; + unsigned num_rounds; +#endif +} br_des_ct_cbcdec_keys; + +/** + * \brief Class instance for DES CBC encryption (`des_ct` implementation). + */ +extern const br_block_cbcenc_class br_des_ct_cbcenc_vtable; + +/** + * \brief Class instance for DES CBC decryption (`des_ct` implementation). + */ +extern const br_block_cbcdec_class br_des_ct_cbcdec_vtable; + +/** + * \brief Context initialisation (key schedule) for DES CBC encryption + * (`des_ct` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_des_ct_cbcenc_init(br_des_ct_cbcenc_keys *ctx, + const void *key, size_t len); + +/** + * \brief Context initialisation (key schedule) for DES CBC decryption + * (`des_ct` implementation). + * + * \param ctx context to initialise. + * \param key secret key. + * \param len secret key length (in bytes). + */ +void br_des_ct_cbcdec_init(br_des_ct_cbcdec_keys *ctx, + const void *key, size_t len); + +/** + * \brief CBC encryption with DES (`des_ct` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to encrypt (updated). + * \param len data length (in bytes, MUST be multiple of 8). + */ +void br_des_ct_cbcenc_run(const br_des_ct_cbcenc_keys *ctx, void *iv, + void *data, size_t len); + +/** + * \brief CBC decryption with DES (`des_ct` implementation). + * + * \param ctx context (already initialised). + * \param iv IV (updated). + * \param data data to decrypt (updated). + * \param len data length (in bytes, MUST be multiple of 8). + */ +void br_des_ct_cbcdec_run(const br_des_ct_cbcdec_keys *ctx, void *iv, + void *data, size_t len); + +/* + * These structures are large enough to accommodate subkeys for all + * DES/3DES implementations. + */ + +/** + * \brief Aggregate structure large enough to be used as context for + * subkeys (CBC encryption) for all DES implementations. + */ +typedef union { + const br_block_cbcenc_class *vtable; + br_des_tab_cbcenc_keys tab; + br_des_ct_cbcenc_keys ct; +} br_des_gen_cbcenc_keys; + +/** + * \brief Aggregate structure large enough to be used as context for + * subkeys (CBC decryption) for all DES implementations. + */ +typedef union { + const br_block_cbcdec_class *vtable; + br_des_tab_cbcdec_keys c_tab; + br_des_ct_cbcdec_keys c_ct; +} br_des_gen_cbcdec_keys; + +/** + * \brief Type for a ChaCha20 implementation. + * + * An implementation follows the description in RFC 7539: + * + * - Key is 256 bits (`key` points to exactly 32 bytes). + * + * - IV is 96 bits (`iv` points to exactly 12 bytes). + * + * - Block counter is over 32 bits and starts at value `cc`; the + * resulting value is returned. + * + * Data (pointed to by `data`, of length `len`) is encrypted/decrypted + * in place. If `len` is not a multiple of 64, then the excess bytes from + * the last block processing are dropped (therefore, "chunked" processing + * works only as long as each non-final chunk has a length multiple of 64). + * + * \param key secret key (32 bytes). + * \param iv IV (12 bytes). + * \param cc initial counter value. + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + */ +typedef uint32_t (*br_chacha20_run)(const void *key, + const void *iv, uint32_t cc, void *data, size_t len); + +/** + * \brief ChaCha20 implementation (straightforward C code, constant-time). + * + * \see br_chacha20_run + * + * \param key secret key (32 bytes). + * \param iv IV (12 bytes). + * \param cc initial counter value. + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + */ +uint32_t br_chacha20_ct_run(const void *key, + const void *iv, uint32_t cc, void *data, size_t len); + +/** + * \brief ChaCha20 implementation (SSE2 code, constant-time). + * + * This implementation is available only on x86 platforms, depending on + * compiler support. Moreover, in 32-bit mode, it might not actually run, + * if the underlying hardware does not implement the SSE2 opcode (in + * 64-bit mode, SSE2 is part of the ABI, so if the code could be compiled + * at all, then it can run). Use `br_chacha20_sse2_get()` to safely obtain + * a pointer to that function. + * + * \see br_chacha20_run + * + * \param key secret key (32 bytes). + * \param iv IV (12 bytes). + * \param cc initial counter value. + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + */ +uint32_t br_chacha20_sse2_run(const void *key, + const void *iv, uint32_t cc, void *data, size_t len); + +/** + * \brief Obtain the `sse2` ChaCha20 implementation, if available. + * + * This function returns a pointer to `br_chacha20_sse2_run`, if + * that implementation was compiled in the library _and_ the SSE2 + * opcodes are available on the currently running CPU. If either of + * these conditions is not met, then this function returns `0`. + * + * \return the `sse2` ChaCha20 implementation, or `0`. + */ +br_chacha20_run br_chacha20_sse2_get(void); + +/** + * \brief Type for a ChaCha20+Poly1305 AEAD implementation. + * + * The provided data is encrypted or decrypted with ChaCha20. The + * authentication tag is computed on the concatenation of the + * additional data and the ciphertext, with the padding and lengths + * as described in RFC 7539 (section 2.8). + * + * After decryption, the caller is responsible for checking that the + * computed tag matches the expected value. + * + * \param key secret key (32 bytes). + * \param iv nonce (12 bytes). + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + * \param aad additional authenticated data. + * \param aad_len length of additional authenticated data (in bytes). + * \param tag output buffer for the authentication tag. + * \param ichacha implementation of ChaCha20. + * \param encrypt non-zero for encryption, zero for decryption. + */ +typedef void (*br_poly1305_run)(const void *key, const void *iv, + void *data, size_t len, const void *aad, size_t aad_len, + void *tag, br_chacha20_run ichacha, int encrypt); + +/** + * \brief ChaCha20+Poly1305 AEAD implementation (mixed 32-bit multiplications). + * + * \see br_poly1305_run + * + * \param key secret key (32 bytes). + * \param iv nonce (12 bytes). + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + * \param aad additional authenticated data. + * \param aad_len length of additional authenticated data (in bytes). + * \param tag output buffer for the authentication tag. + * \param ichacha implementation of ChaCha20. + * \param encrypt non-zero for encryption, zero for decryption. + */ +void br_poly1305_ctmul_run(const void *key, const void *iv, + void *data, size_t len, const void *aad, size_t aad_len, + void *tag, br_chacha20_run ichacha, int encrypt); + +/** + * \brief ChaCha20+Poly1305 AEAD implementation (pure 32-bit multiplications). + * + * \see br_poly1305_run + * + * \param key secret key (32 bytes). + * \param iv nonce (12 bytes). + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + * \param aad additional authenticated data. + * \param aad_len length of additional authenticated data (in bytes). + * \param tag output buffer for the authentication tag. + * \param ichacha implementation of ChaCha20. + * \param encrypt non-zero for encryption, zero for decryption. + */ +void br_poly1305_ctmul32_run(const void *key, const void *iv, + void *data, size_t len, const void *aad, size_t aad_len, + void *tag, br_chacha20_run ichacha, int encrypt); + +/** + * \brief ChaCha20+Poly1305 AEAD implementation (i15). + * + * This implementation relies on the generic big integer code "i15" + * (which uses pure 32-bit multiplications). As such, it may save a + * little code footprint in a context where "i15" is already included + * (e.g. for elliptic curves or for RSA); however, it is also + * substantially slower than the ctmul and ctmul32 implementations. + * + * \see br_poly1305_run + * + * \param key secret key (32 bytes). + * \param iv nonce (12 bytes). + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + * \param aad additional authenticated data. + * \param aad_len length of additional authenticated data (in bytes). + * \param tag output buffer for the authentication tag. + * \param ichacha implementation of ChaCha20. + * \param encrypt non-zero for encryption, zero for decryption. + */ +void br_poly1305_i15_run(const void *key, const void *iv, + void *data, size_t len, const void *aad, size_t aad_len, + void *tag, br_chacha20_run ichacha, int encrypt); + +/** + * \brief ChaCha20+Poly1305 AEAD implementation (ctmulq). + * + * This implementation uses 64-bit multiplications (result over 128 bits). + * It is available only on platforms that offer such a primitive (in + * practice, 64-bit architectures). Use `br_poly1305_ctmulq_get()` to + * dynamically obtain a pointer to that function, or 0 if not supported. + * + * \see br_poly1305_run + * + * \param key secret key (32 bytes). + * \param iv nonce (12 bytes). + * \param data data to encrypt or decrypt. + * \param len data length (in bytes). + * \param aad additional authenticated data. + * \param aad_len length of additional authenticated data (in bytes). + * \param tag output buffer for the authentication tag. + * \param ichacha implementation of ChaCha20. + * \param encrypt non-zero for encryption, zero for decryption. + */ +void br_poly1305_ctmulq_run(const void *key, const void *iv, + void *data, size_t len, const void *aad, size_t aad_len, + void *tag, br_chacha20_run ichacha, int encrypt); + +/** + * \brief Get the ChaCha20+Poly1305 "ctmulq" implementation, if available. + * + * This function returns a pointer to the `br_poly1305_ctmulq_run()` + * function if supported on the current platform; otherwise, it returns 0. + * + * \return the ctmulq ChaCha20+Poly1305 implementation, or 0. + */ +br_poly1305_run br_poly1305_ctmulq_get(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/enclave/include/bearssl/bearssl_ec.h b/enclave/include/bearssl/bearssl_ec.h new file mode 100644 index 0000000..f954309 --- /dev/null +++ b/enclave/include/bearssl/bearssl_ec.h @@ -0,0 +1,967 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_EC_H__ +#define BR_BEARSSL_EC_H__ + +#include +#include + +#include "bearssl_rand.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file bearssl_ec.h + * + * # Elliptic Curves + * + * This file documents the EC implementations provided with BearSSL, and + * ECDSA. + * + * ## Elliptic Curve API + * + * Only "named curves" are supported. Each EC implementation supports + * one or several named curves, identified by symbolic identifiers. + * These identifiers are small integers, that correspond to the values + * registered by the + * [IANA](http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8). + * + * Since all currently defined elliptic curve identifiers are in the 0..31 + * range, it is convenient to encode support of some curves in a 32-bit + * word, such that bit x corresponds to curve of identifier x. + * + * An EC implementation is incarnated by a `br_ec_impl` instance, that + * offers the following fields: + * + * - `supported_curves` + * + * A 32-bit word that documents the identifiers of the curves supported + * by this implementation. + * + * - `generator()` + * + * Callback method that returns a pointer to the conventional generator + * point for that curve. + * + * - `order()` + * + * Callback method that returns a pointer to the subgroup order for + * that curve. That value uses unsigned big-endian encoding. + * + * - `xoff()` + * + * Callback method that returns the offset and length of the X + * coordinate in an encoded point. + * + * - `mul()` + * + * Multiply a curve point with an integer. + * + * - `mulgen()` + * + * Multiply the curve generator with an integer. This may be faster + * than the generic `mul()`. + * + * - `muladd()` + * + * Multiply two curve points by two integers, and return the sum of + * the two products. + * + * All curve points are represented in uncompressed format. The `mul()` + * and `muladd()` methods take care to validate that the provided points + * are really part of the relevant curve subgroup. + * + * For all point multiplication functions, the following holds: + * + * - Functions validate that the provided points are valid members + * of the relevant curve subgroup. An error is reported if that is + * not the case. + * + * - Processing is constant-time, even if the point operands are not + * valid. This holds for both the source and resulting points, and + * the multipliers (integers). Only the byte length of the provided + * multiplier arrays (not their actual value length in bits) may + * leak through timing-based side channels. + * + * - The multipliers (integers) MUST be lower than the subgroup order. + * If this property is not met, then the result is indeterminate, + * but an error value is not ncessearily returned. + * + * + * ## ECDSA + * + * ECDSA signatures have two standard formats, called "raw" and "asn1". + * Internally, such a signature is a pair of modular integers `(r,s)`. + * The "raw" format is the concatenation of the unsigned big-endian + * encodings of these two integers, possibly left-padded with zeros so + * that they have the same encoded length. The "asn1" format is the + * DER encoding of an ASN.1 structure that contains the two integer + * values: + * + * ECDSASignature ::= SEQUENCE { + * r INTEGER, + * s INTEGER + * } + * + * In general, in all of X.509 and SSL/TLS, the "asn1" format is used. + * BearSSL offers ECDSA implementations for both formats; conversion + * functions between the two formats are also provided. Conversion of a + * "raw" format signature into "asn1" may enlarge a signature by no more + * than 9 bytes for all supported curves; conversely, conversion of an + * "asn1" signature to "raw" may expand the signature but the "raw" + * length will never be more than twice the length of the "asn1" length + * (and usually it will be shorter). + * + * Note that for a given signature, the "raw" format is not fully + * deterministic, in that it does not enforce a minimal common length. + */ + +/* + * Standard curve ID. These ID are equal to the assigned numerical + * identifiers assigned to these curves for TLS: + * http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 + */ + +/** \brief Identifier for named curve sect163k1. */ +#define BR_EC_sect163k1 1 + +/** \brief Identifier for named curve sect163r1. */ +#define BR_EC_sect163r1 2 + +/** \brief Identifier for named curve sect163r2. */ +#define BR_EC_sect163r2 3 + +/** \brief Identifier for named curve sect193r1. */ +#define BR_EC_sect193r1 4 + +/** \brief Identifier for named curve sect193r2. */ +#define BR_EC_sect193r2 5 + +/** \brief Identifier for named curve sect233k1. */ +#define BR_EC_sect233k1 6 + +/** \brief Identifier for named curve sect233r1. */ +#define BR_EC_sect233r1 7 + +/** \brief Identifier for named curve sect239k1. */ +#define BR_EC_sect239k1 8 + +/** \brief Identifier for named curve sect283k1. */ +#define BR_EC_sect283k1 9 + +/** \brief Identifier for named curve sect283r1. */ +#define BR_EC_sect283r1 10 + +/** \brief Identifier for named curve sect409k1. */ +#define BR_EC_sect409k1 11 + +/** \brief Identifier for named curve sect409r1. */ +#define BR_EC_sect409r1 12 + +/** \brief Identifier for named curve sect571k1. */ +#define BR_EC_sect571k1 13 + +/** \brief Identifier for named curve sect571r1. */ +#define BR_EC_sect571r1 14 + +/** \brief Identifier for named curve secp160k1. */ +#define BR_EC_secp160k1 15 + +/** \brief Identifier for named curve secp160r1. */ +#define BR_EC_secp160r1 16 + +/** \brief Identifier for named curve secp160r2. */ +#define BR_EC_secp160r2 17 + +/** \brief Identifier for named curve secp192k1. */ +#define BR_EC_secp192k1 18 + +/** \brief Identifier for named curve secp192r1. */ +#define BR_EC_secp192r1 19 + +/** \brief Identifier for named curve secp224k1. */ +#define BR_EC_secp224k1 20 + +/** \brief Identifier for named curve secp224r1. */ +#define BR_EC_secp224r1 21 + +/** \brief Identifier for named curve secp256k1. */ +#define BR_EC_secp256k1 22 + +/** \brief Identifier for named curve secp256r1. */ +#define BR_EC_secp256r1 23 + +/** \brief Identifier for named curve secp384r1. */ +#define BR_EC_secp384r1 24 + +/** \brief Identifier for named curve secp521r1. */ +#define BR_EC_secp521r1 25 + +/** \brief Identifier for named curve brainpoolP256r1. */ +#define BR_EC_brainpoolP256r1 26 + +/** \brief Identifier for named curve brainpoolP384r1. */ +#define BR_EC_brainpoolP384r1 27 + +/** \brief Identifier for named curve brainpoolP512r1. */ +#define BR_EC_brainpoolP512r1 28 + +/** \brief Identifier for named curve Curve25519. */ +#define BR_EC_curve25519 29 + +/** \brief Identifier for named curve Curve448. */ +#define BR_EC_curve448 30 + +/** + * \brief Structure for an EC public key. + */ +typedef struct { + /** \brief Identifier for the curve used by this key. */ + int curve; + /** \brief Public curve point (uncompressed format). */ + unsigned char *q; + /** \brief Length of public curve point (in bytes). */ + size_t qlen; +} br_ec_public_key; + +/** + * \brief Structure for an EC private key. + * + * The private key is an integer modulo the curve subgroup order. The + * encoding below tolerates extra leading zeros. In general, it is + * recommended that the private key has the same length as the curve + * subgroup order. + */ +typedef struct { + /** \brief Identifier for the curve used by this key. */ + int curve; + /** \brief Private key (integer, unsigned big-endian encoding). */ + unsigned char *x; + /** \brief Private key length (in bytes). */ + size_t xlen; +} br_ec_private_key; + +/** + * \brief Type for an EC implementation. + */ +typedef struct { + /** + * \brief Supported curves. + * + * This word is a bitfield: bit `x` is set if the curve of ID `x` + * is supported. E.g. an implementation supporting both NIST P-256 + * (secp256r1, ID 23) and NIST P-384 (secp384r1, ID 24) will have + * value `0x01800000` in this field. + */ + uint32_t supported_curves; + + /** + * \brief Get the conventional generator. + * + * This function returns the conventional generator (encoded + * curve point) for the specified curve. This function MUST NOT + * be called if the curve is not supported. + * + * \param curve curve identifier. + * \param len receiver for the encoded generator length (in bytes). + * \return the encoded generator. + */ + const unsigned char *(*generator)(int curve, size_t *len); + + /** + * \brief Get the subgroup order. + * + * This function returns the order of the subgroup generated by + * the conventional generator, for the specified curve. Unsigned + * big-endian encoding is used. This function MUST NOT be called + * if the curve is not supported. + * + * \param curve curve identifier. + * \param len receiver for the encoded order length (in bytes). + * \return the encoded order. + */ + const unsigned char *(*order)(int curve, size_t *len); + + /** + * \brief Get the offset and length for the X coordinate. + * + * This function returns the offset and length (in bytes) of + * the X coordinate in an encoded non-zero point. + * + * \param curve curve identifier. + * \param len receiver for the X coordinate length (in bytes). + * \return the offset for the X coordinate (in bytes). + */ + size_t (*xoff)(int curve, size_t *len); + + /** + * \brief Multiply a curve point by an integer. + * + * The source point is provided in array `G` (of size `Glen` bytes); + * the multiplication result is written over it. The multiplier + * `x` (of size `xlen` bytes) uses unsigned big-endian encoding. + * + * Rules: + * + * - The specified curve MUST be supported. + * + * - The source point must be a valid point on the relevant curve + * subgroup (and not the "point at infinity" either). If this is + * not the case, then this function returns an error (0). + * + * - The multiplier integer MUST be non-zero and less than the + * curve subgroup order. If this property does not hold, then + * the result is indeterminate and an error code is not + * guaranteed. + * + * Returned value is 1 on success, 0 on error. On error, the + * contents of `G` are indeterminate. + * + * \param G point to multiply. + * \param Glen length of the encoded point (in bytes). + * \param x multiplier (unsigned big-endian). + * \param xlen multiplier length (in bytes). + * \param curve curve identifier. + * \return 1 on success, 0 on error. + */ + uint32_t (*mul)(unsigned char *G, size_t Glen, + const unsigned char *x, size_t xlen, int curve); + + /** + * \brief Multiply the generator by an integer. + * + * The multiplier MUST be non-zero and less than the curve + * subgroup order. Results are indeterminate if this property + * does not hold. + * + * \param R output buffer for the point. + * \param x multiplier (unsigned big-endian). + * \param xlen multiplier length (in bytes). + * \param curve curve identifier. + * \return encoded result point length (in bytes). + */ + size_t (*mulgen)(unsigned char *R, + const unsigned char *x, size_t xlen, int curve); + + /** + * \brief Multiply two points by two integers and add the + * results. + * + * The point `x*A + y*B` is computed and written back in the `A` + * array. + * + * Rules: + * + * - The specified curve MUST be supported. + * + * - The source points (`A` and `B`) must be valid points on + * the relevant curve subgroup (and not the "point at + * infinity" either). If this is not the case, then this + * function returns an error (0). + * + * - If the `B` pointer is `NULL`, then the conventional + * subgroup generator is used. With some implementations, + * this may be faster than providing a pointer to the + * generator. + * + * - The multiplier integers (`x` and `y`) MUST be non-zero + * and less than the curve subgroup order. If either integer + * is zero, then an error is reported, but if one of them is + * not lower than the subgroup order, then the result is + * indeterminate and an error code is not guaranteed. + * + * - If the final result is the point at infinity, then an + * error is returned. + * + * Returned value is 1 on success, 0 on error. On error, the + * contents of `A` are indeterminate. + * + * \param A first point to multiply. + * \param B second point to multiply (`NULL` for the generator). + * \param len common length of the encoded points (in bytes). + * \param x multiplier for `A` (unsigned big-endian). + * \param xlen length of multiplier for `A` (in bytes). + * \param y multiplier for `A` (unsigned big-endian). + * \param ylen length of multiplier for `A` (in bytes). + * \param curve curve identifier. + * \return 1 on success, 0 on error. + */ + uint32_t (*muladd)(unsigned char *A, const unsigned char *B, size_t len, + const unsigned char *x, size_t xlen, + const unsigned char *y, size_t ylen, int curve); +} br_ec_impl; + +/** + * \brief EC implementation "i31". + * + * This implementation internally uses generic code for modular integers, + * with a representation as sequences of 31-bit words. It supports secp256r1, + * secp384r1 and secp521r1 (aka NIST curves P-256, P-384 and P-521). + */ +extern const br_ec_impl br_ec_prime_i31; + +/** + * \brief EC implementation "i15". + * + * This implementation internally uses generic code for modular integers, + * with a representation as sequences of 15-bit words. It supports secp256r1, + * secp384r1 and secp521r1 (aka NIST curves P-256, P-384 and P-521). + */ +extern const br_ec_impl br_ec_prime_i15; + +/** + * \brief EC implementation "m15" for P-256. + * + * This implementation uses specialised code for curve secp256r1 (also + * known as NIST P-256), with optional Karatsuba decomposition, and fast + * modular reduction thanks to the field modulus special format. Only + * 32-bit multiplications are used (with 32-bit results, not 64-bit). + */ +extern const br_ec_impl br_ec_p256_m15; + +/** + * \brief EC implementation "m31" for P-256. + * + * This implementation uses specialised code for curve secp256r1 (also + * known as NIST P-256), relying on multiplications of 31-bit values + * (MUL31). + */ +extern const br_ec_impl br_ec_p256_m31; + +/** + * \brief EC implementation "m62" (specialised code) for P-256. + * + * This implementation uses custom code relying on multiplication of + * integers up to 64 bits, with a 128-bit result. This implementation is + * defined only on platforms that offer the 64x64->128 multiplication + * support; use `br_ec_p256_m62_get()` to dynamically obtain a pointer + * to that implementation. + */ +extern const br_ec_impl br_ec_p256_m62; + +/** + * \brief Get the "m62" implementation of P-256, if available. + * + * \return the implementation, or 0. + */ +const br_ec_impl *br_ec_p256_m62_get(void); + +/** + * \brief EC implementation "m64" (specialised code) for P-256. + * + * This implementation uses custom code relying on multiplication of + * integers up to 64 bits, with a 128-bit result. This implementation is + * defined only on platforms that offer the 64x64->128 multiplication + * support; use `br_ec_p256_m64_get()` to dynamically obtain a pointer + * to that implementation. + */ +extern const br_ec_impl br_ec_p256_m64; + +/** + * \brief Get the "m64" implementation of P-256, if available. + * + * \return the implementation, or 0. + */ +const br_ec_impl *br_ec_p256_m64_get(void); + +/** + * \brief EC implementation "i15" (generic code) for Curve25519. + * + * This implementation uses the generic code for modular integers (with + * 15-bit words) to support Curve25519. Due to the specificities of the + * curve definition, the following applies: + * + * - `muladd()` is not implemented (the function returns 0 systematically). + * - `order()` returns 2^255-1, since the point multiplication algorithm + * accepts any 32-bit integer as input (it clears the top bit and low + * three bits systematically). + */ +extern const br_ec_impl br_ec_c25519_i15; + +/** + * \brief EC implementation "i31" (generic code) for Curve25519. + * + * This implementation uses the generic code for modular integers (with + * 31-bit words) to support Curve25519. Due to the specificities of the + * curve definition, the following applies: + * + * - `muladd()` is not implemented (the function returns 0 systematically). + * - `order()` returns 2^255-1, since the point multiplication algorithm + * accepts any 32-bit integer as input (it clears the top bit and low + * three bits systematically). + */ +extern const br_ec_impl br_ec_c25519_i31; + +/** + * \brief EC implementation "m15" (specialised code) for Curve25519. + * + * This implementation uses custom code relying on multiplication of + * integers up to 15 bits. Due to the specificities of the curve + * definition, the following applies: + * + * - `muladd()` is not implemented (the function returns 0 systematically). + * - `order()` returns 2^255-1, since the point multiplication algorithm + * accepts any 32-bit integer as input (it clears the top bit and low + * three bits systematically). + */ +extern const br_ec_impl br_ec_c25519_m15; + +/** + * \brief EC implementation "m31" (specialised code) for Curve25519. + * + * This implementation uses custom code relying on multiplication of + * integers up to 31 bits. Due to the specificities of the curve + * definition, the following applies: + * + * - `muladd()` is not implemented (the function returns 0 systematically). + * - `order()` returns 2^255-1, since the point multiplication algorithm + * accepts any 32-bit integer as input (it clears the top bit and low + * three bits systematically). + */ +extern const br_ec_impl br_ec_c25519_m31; + +/** + * \brief EC implementation "m62" (specialised code) for Curve25519. + * + * This implementation uses custom code relying on multiplication of + * integers up to 62 bits, with a 124-bit result. This implementation is + * defined only on platforms that offer the 64x64->128 multiplication + * support; use `br_ec_c25519_m62_get()` to dynamically obtain a pointer + * to that implementation. Due to the specificities of the curve + * definition, the following applies: + * + * - `muladd()` is not implemented (the function returns 0 systematically). + * - `order()` returns 2^255-1, since the point multiplication algorithm + * accepts any 32-bit integer as input (it clears the top bit and low + * three bits systematically). + */ +extern const br_ec_impl br_ec_c25519_m62; + +/** + * \brief Get the "m62" implementation of Curve25519, if available. + * + * \return the implementation, or 0. + */ +const br_ec_impl *br_ec_c25519_m62_get(void); + +/** + * \brief EC implementation "m64" (specialised code) for Curve25519. + * + * This implementation uses custom code relying on multiplication of + * integers up to 64 bits, with a 128-bit result. This implementation is + * defined only on platforms that offer the 64x64->128 multiplication + * support; use `br_ec_c25519_m64_get()` to dynamically obtain a pointer + * to that implementation. Due to the specificities of the curve + * definition, the following applies: + * + * - `muladd()` is not implemented (the function returns 0 systematically). + * - `order()` returns 2^255-1, since the point multiplication algorithm + * accepts any 32-bit integer as input (it clears the top bit and low + * three bits systematically). + */ +extern const br_ec_impl br_ec_c25519_m64; + +/** + * \brief Get the "m64" implementation of Curve25519, if available. + * + * \return the implementation, or 0. + */ +const br_ec_impl *br_ec_c25519_m64_get(void); + +/** + * \brief Aggregate EC implementation "m15". + * + * This implementation is a wrapper for: + * + * - `br_ec_c25519_m15` for Curve25519 + * - `br_ec_p256_m15` for NIST P-256 + * - `br_ec_prime_i15` for other curves (NIST P-384 and NIST-P512) + */ +extern const br_ec_impl br_ec_all_m15; + +/** + * \brief Aggregate EC implementation "m31". + * + * This implementation is a wrapper for: + * + * - `br_ec_c25519_m31` for Curve25519 + * - `br_ec_p256_m31` for NIST P-256 + * - `br_ec_prime_i31` for other curves (NIST P-384 and NIST-P512) + */ +extern const br_ec_impl br_ec_all_m31; + +/** + * \brief Get the "default" EC implementation for the current system. + * + * This returns a pointer to the preferred implementation on the + * current system. + * + * \return the default EC implementation. + */ +const br_ec_impl *br_ec_get_default(void); + +/** + * \brief Convert a signature from "raw" to "asn1". + * + * Conversion is done "in place" and the new length is returned. + * Conversion may enlarge the signature, but by no more than 9 bytes at + * most. On error, 0 is returned (error conditions include an odd raw + * signature length, or an oversized integer). + * + * \param sig signature to convert. + * \param sig_len signature length (in bytes). + * \return the new signature length, or 0 on error. + */ +size_t br_ecdsa_raw_to_asn1(void *sig, size_t sig_len); + +/** + * \brief Convert a signature from "asn1" to "raw". + * + * Conversion is done "in place" and the new length is returned. + * Conversion may enlarge the signature, but the new signature length + * will be less than twice the source length at most. On error, 0 is + * returned (error conditions include an invalid ASN.1 structure or an + * oversized integer). + * + * \param sig signature to convert. + * \param sig_len signature length (in bytes). + * \return the new signature length, or 0 on error. + */ +size_t br_ecdsa_asn1_to_raw(void *sig, size_t sig_len); + +/** + * \brief Type for an ECDSA signer function. + * + * A pointer to the EC implementation is provided. The hash value is + * assumed to have the length inferred from the designated hash function + * class. + * + * Signature is written in the buffer pointed to by `sig`, and the length + * (in bytes) is returned. On error, nothing is written in the buffer, + * and 0 is returned. This function returns 0 if the specified curve is + * not supported by the provided EC implementation. + * + * The signature format is either "raw" or "asn1", depending on the + * implementation; maximum length is predictable from the implemented + * curve: + * + * | curve | raw | asn1 | + * | :--------- | --: | ---: | + * | NIST P-256 | 64 | 72 | + * | NIST P-384 | 96 | 104 | + * | NIST P-521 | 132 | 139 | + * + * \param impl EC implementation to use. + * \param hf hash function used to process the data. + * \param hash_value signed data (hashed). + * \param sk EC private key. + * \param sig destination buffer. + * \return the signature length (in bytes), or 0 on error. + */ +typedef size_t (*br_ecdsa_sign)(const br_ec_impl *impl, + const br_hash_class *hf, const void *hash_value, + const br_ec_private_key *sk, void *sig); + +/** + * \brief Type for an ECDSA signature verification function. + * + * A pointer to the EC implementation is provided. The hashed value, + * computed over the purportedly signed data, is also provided with + * its length. + * + * The signature format is either "raw" or "asn1", depending on the + * implementation. + * + * Returned value is 1 on success (valid signature), 0 on error. This + * function returns 0 if the specified curve is not supported by the + * provided EC implementation. + * + * \param impl EC implementation to use. + * \param hash signed data (hashed). + * \param hash_len hash value length (in bytes). + * \param pk EC public key. + * \param sig signature. + * \param sig_len signature length (in bytes). + * \return 1 on success, 0 on error. + */ +typedef uint32_t (*br_ecdsa_vrfy)(const br_ec_impl *impl, + const void *hash, size_t hash_len, + const br_ec_public_key *pk, const void *sig, size_t sig_len); + +/** + * \brief ECDSA signature generator, "i31" implementation, "asn1" format. + * + * \see br_ecdsa_sign() + * + * \param impl EC implementation to use. + * \param hf hash function used to process the data. + * \param hash_value signed data (hashed). + * \param sk EC private key. + * \param sig destination buffer. + * \return the signature length (in bytes), or 0 on error. + */ +size_t br_ecdsa_i31_sign_asn1(const br_ec_impl *impl, + const br_hash_class *hf, const void *hash_value, + const br_ec_private_key *sk, void *sig); + +/** + * \brief ECDSA signature generator, "i31" implementation, "raw" format. + * + * \see br_ecdsa_sign() + * + * \param impl EC implementation to use. + * \param hf hash function used to process the data. + * \param hash_value signed data (hashed). + * \param sk EC private key. + * \param sig destination buffer. + * \return the signature length (in bytes), or 0 on error. + */ +size_t br_ecdsa_i31_sign_raw(const br_ec_impl *impl, + const br_hash_class *hf, const void *hash_value, + const br_ec_private_key *sk, void *sig); + +/** + * \brief ECDSA signature verifier, "i31" implementation, "asn1" format. + * + * \see br_ecdsa_vrfy() + * + * \param impl EC implementation to use. + * \param hash signed data (hashed). + * \param hash_len hash value length (in bytes). + * \param pk EC public key. + * \param sig signature. + * \param sig_len signature length (in bytes). + * \return 1 on success, 0 on error. + */ +uint32_t br_ecdsa_i31_vrfy_asn1(const br_ec_impl *impl, + const void *hash, size_t hash_len, + const br_ec_public_key *pk, const void *sig, size_t sig_len); + +/** + * \brief ECDSA signature verifier, "i31" implementation, "raw" format. + * + * \see br_ecdsa_vrfy() + * + * \param impl EC implementation to use. + * \param hash signed data (hashed). + * \param hash_len hash value length (in bytes). + * \param pk EC public key. + * \param sig signature. + * \param sig_len signature length (in bytes). + * \return 1 on success, 0 on error. + */ +uint32_t br_ecdsa_i31_vrfy_raw(const br_ec_impl *impl, + const void *hash, size_t hash_len, + const br_ec_public_key *pk, const void *sig, size_t sig_len); + +/** + * \brief ECDSA signature generator, "i15" implementation, "asn1" format. + * + * \see br_ecdsa_sign() + * + * \param impl EC implementation to use. + * \param hf hash function used to process the data. + * \param hash_value signed data (hashed). + * \param sk EC private key. + * \param sig destination buffer. + * \return the signature length (in bytes), or 0 on error. + */ +size_t br_ecdsa_i15_sign_asn1(const br_ec_impl *impl, + const br_hash_class *hf, const void *hash_value, + const br_ec_private_key *sk, void *sig); + +/** + * \brief ECDSA signature generator, "i15" implementation, "raw" format. + * + * \see br_ecdsa_sign() + * + * \param impl EC implementation to use. + * \param hf hash function used to process the data. + * \param hash_value signed data (hashed). + * \param sk EC private key. + * \param sig destination buffer. + * \return the signature length (in bytes), or 0 on error. + */ +size_t br_ecdsa_i15_sign_raw(const br_ec_impl *impl, + const br_hash_class *hf, const void *hash_value, + const br_ec_private_key *sk, void *sig); + +/** + * \brief ECDSA signature verifier, "i15" implementation, "asn1" format. + * + * \see br_ecdsa_vrfy() + * + * \param impl EC implementation to use. + * \param hash signed data (hashed). + * \param hash_len hash value length (in bytes). + * \param pk EC public key. + * \param sig signature. + * \param sig_len signature length (in bytes). + * \return 1 on success, 0 on error. + */ +uint32_t br_ecdsa_i15_vrfy_asn1(const br_ec_impl *impl, + const void *hash, size_t hash_len, + const br_ec_public_key *pk, const void *sig, size_t sig_len); + +/** + * \brief ECDSA signature verifier, "i15" implementation, "raw" format. + * + * \see br_ecdsa_vrfy() + * + * \param impl EC implementation to use. + * \param hash signed data (hashed). + * \param hash_len hash value length (in bytes). + * \param pk EC public key. + * \param sig signature. + * \param sig_len signature length (in bytes). + * \return 1 on success, 0 on error. + */ +uint32_t br_ecdsa_i15_vrfy_raw(const br_ec_impl *impl, + const void *hash, size_t hash_len, + const br_ec_public_key *pk, const void *sig, size_t sig_len); + +/** + * \brief Get "default" ECDSA implementation (signer, asn1 format). + * + * This returns the preferred implementation of ECDSA signature generation + * ("asn1" output format) on the current system. + * + * \return the default implementation. + */ +br_ecdsa_sign br_ecdsa_sign_asn1_get_default(void); + +/** + * \brief Get "default" ECDSA implementation (signer, raw format). + * + * This returns the preferred implementation of ECDSA signature generation + * ("raw" output format) on the current system. + * + * \return the default implementation. + */ +br_ecdsa_sign br_ecdsa_sign_raw_get_default(void); + +/** + * \brief Get "default" ECDSA implementation (verifier, asn1 format). + * + * This returns the preferred implementation of ECDSA signature verification + * ("asn1" output format) on the current system. + * + * \return the default implementation. + */ +br_ecdsa_vrfy br_ecdsa_vrfy_asn1_get_default(void); + +/** + * \brief Get "default" ECDSA implementation (verifier, raw format). + * + * This returns the preferred implementation of ECDSA signature verification + * ("raw" output format) on the current system. + * + * \return the default implementation. + */ +br_ecdsa_vrfy br_ecdsa_vrfy_raw_get_default(void); + +/** + * \brief Maximum size for EC private key element buffer. + * + * This is the largest number of bytes that `br_ec_keygen()` may need or + * ever return. + */ +#define BR_EC_KBUF_PRIV_MAX_SIZE 72 + +/** + * \brief Maximum size for EC public key element buffer. + * + * This is the largest number of bytes that `br_ec_compute_public()` may + * need or ever return. + */ +#define BR_EC_KBUF_PUB_MAX_SIZE 145 + +/** + * \brief Generate a new EC private key. + * + * If the specified `curve` is not supported by the elliptic curve + * implementation (`impl`), then this function returns zero. + * + * The `sk` structure fields are set to the new private key data. In + * particular, `sk.x` is made to point to the provided key buffer (`kbuf`), + * in which the actual private key data is written. That buffer is assumed + * to be large enough. The `BR_EC_KBUF_PRIV_MAX_SIZE` defines the maximum + * size for all supported curves. + * + * The number of bytes used in `kbuf` is returned. If `kbuf` is `NULL`, then + * the private key is not actually generated, and `sk` may also be `NULL`; + * the minimum length for `kbuf` is still computed and returned. + * + * If `sk` is `NULL` but `kbuf` is not `NULL`, then the private key is + * still generated and stored in `kbuf`. + * + * \param rng_ctx source PRNG context (already initialized). + * \param impl the elliptic curve implementation. + * \param sk the private key structure to fill, or `NULL`. + * \param kbuf the key element buffer, or `NULL`. + * \param curve the curve identifier. + * \return the key data length (in bytes), or zero. + */ +size_t br_ec_keygen(const br_prng_class **rng_ctx, + const br_ec_impl *impl, br_ec_private_key *sk, + void *kbuf, int curve); + +/** + * \brief Compute EC public key from EC private key. + * + * This function uses the provided elliptic curve implementation (`impl`) + * to compute the public key corresponding to the private key held in `sk`. + * The public key point is written into `kbuf`, which is then linked from + * the `*pk` structure. The size of the public key point, i.e. the number + * of bytes used in `kbuf`, is returned. + * + * If `kbuf` is `NULL`, then the public key point is NOT computed, and + * the public key structure `*pk` is unmodified (`pk` may be `NULL` in + * that case). The size of the public key point is still returned. + * + * If `pk` is `NULL` but `kbuf` is not `NULL`, then the public key + * point is computed and stored in `kbuf`, and its size is returned. + * + * If the curve used by the private key is not supported by the curve + * implementation, then this function returns zero. + * + * The private key MUST be valid. An off-range private key value is not + * necessarily detected, and leads to unpredictable results. + * + * \param impl the elliptic curve implementation. + * \param pk the public key structure to fill (or `NULL`). + * \param kbuf the public key point buffer (or `NULL`). + * \param sk the source private key. + * \return the public key point length (in bytes), or zero. + */ +size_t br_ec_compute_pub(const br_ec_impl *impl, br_ec_public_key *pk, + void *kbuf, const br_ec_private_key *sk); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/enclave/include/bearssl/bearssl_hash.h b/enclave/include/bearssl/bearssl_hash.h new file mode 100644 index 0000000..ca4fa26 --- /dev/null +++ b/enclave/include/bearssl/bearssl_hash.h @@ -0,0 +1,1346 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_HASH_H__ +#define BR_BEARSSL_HASH_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file bearssl_hash.h + * + * # Hash Functions + * + * This file documents the API for hash functions. + * + * + * ## Procedural API + * + * For each implemented hash function, of name "`xxx`", the following + * elements are defined: + * + * - `br_xxx_vtable` + * + * An externally defined instance of `br_hash_class`. + * + * - `br_xxx_SIZE` + * + * A macro that evaluates to the output size (in bytes) of the + * hash function. + * + * - `br_xxx_ID` + * + * A macro that evaluates to a symbolic identifier for the hash + * function. Such identifiers are used with HMAC and signature + * algorithm implementations. + * + * NOTE: for the "standard" hash functions defined in [the TLS + * standard](https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1), + * the symbolic identifiers match the constants used in TLS, i.e. + * 1 to 6 for MD5, SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512, + * respectively. + * + * - `br_xxx_context` + * + * Context for an ongoing computation. It is allocated by the + * caller, and a pointer to it is passed to all functions. A + * context contains no interior pointer, so it can be moved around + * and cloned (with a simple `memcpy()` or equivalent) in order to + * capture the function state at some point. Computations that use + * distinct context structures are independent of each other. The + * first field of `br_xxx_context` is always a pointer to the + * `br_xxx_vtable` structure; `br_xxx_init()` sets that pointer. + * + * - `br_xxx_init(br_xxx_context *ctx)` + * + * Initialise the provided context. Previous contents of the structure + * are ignored. This calls resets the context to the start of a new + * hash computation; it also sets the first field of the context + * structure (called `vtable`) to a pointer to the statically + * allocated constant `br_xxx_vtable` structure. + * + * - `br_xxx_update(br_xxx_context *ctx, const void *data, size_t len)` + * + * Add some more bytes to the hash computation represented by the + * provided context. + * + * - `br_xxx_out(const br_xxx_context *ctx, void *out)` + * + * Complete the hash computation and write the result in the provided + * buffer. The output buffer MUST be large enough to accommodate the + * result. The context is NOT modified by this operation, so this + * function can be used to get a "partial hash" while still keeping + * the possibility of adding more bytes to the input. + * + * - `br_xxx_state(const br_xxx_context *ctx, void *out)` + * + * Get a copy of the "current state" for the computation so far. For + * MD functions (MD5, SHA-1, SHA-2 family), this is the running state + * resulting from the processing of the last complete input block. + * Returned value is the current input length (in bytes). + * + * - `br_xxx_set_state(br_xxx_context *ctx, const void *stb, uint64_t count)` + * + * Set the internal state to the provided values. The 'stb' and + * 'count' values shall match that which was obtained from + * `br_xxx_state()`. This restores the hash state only if the state + * values were at an appropriate block boundary. This does NOT set + * the `vtable` pointer in the context. + * + * Context structures can be discarded without any explicit deallocation. + * Hash function implementations are purely software and don't reserve + * any resources outside of the context structure itself. + * + * + * ## Object-Oriented API + * + * For each hash function that follows the procedural API described + * above, an object-oriented API is also provided. In that API, function + * pointers from the vtable (`br_xxx_vtable`) are used. The vtable + * incarnates object-oriented programming. An introduction on the OOP + * concept used here can be read on the BearSSL Web site:
+ *    [https://www.bearssl.org/oop.html](https://www.bearssl.org/oop.html) + * + * The vtable offers functions called `init()`, `update()`, `out()`, + * `set()` and `set_state()`, which are in fact the functions from + * the procedural API. That vtable also contains two informative fields: + * + * - `context_size` + * + * The size of the context structure (`br_xxx_context`), in bytes. + * This can be used by generic implementations to perform dynamic + * context allocation. + * + * - `desc` + * + * A "descriptor" field that encodes some information on the hash + * function: symbolic identifier, output size, state size, + * internal block size, details on the padding. + * + * Users of this object-oriented API (in particular generic HMAC + * implementations) may make the following assumptions: + * + * - Hash output size is no more than 64 bytes. + * - Hash internal state size is no more than 64 bytes. + * - Internal block size is a power of two, no less than 16 and no more + * than 256. + * + * + * ## Implemented Hash Functions + * + * Implemented hash functions are: + * + * | Function | Name | Output length | State length | + * | :-------- | :------ | :-----------: | :----------: | + * | MD5 | md5 | 16 | 16 | + * | SHA-1 | sha1 | 20 | 20 | + * | SHA-224 | sha224 | 28 | 32 | + * | SHA-256 | sha256 | 32 | 32 | + * | SHA-384 | sha384 | 48 | 64 | + * | SHA-512 | sha512 | 64 | 64 | + * | MD5+SHA-1 | md5sha1 | 36 | 36 | + * + * (MD5+SHA-1 is the concatenation of MD5 and SHA-1 computed over the + * same input; in the implementation, the internal data buffer is + * shared, thus making it more memory-efficient than separate MD5 and + * SHA-1. It can be useful in implementing SSL 3.0, TLS 1.0 and TLS + * 1.1.) + * + * + * ## Multi-Hasher + * + * An aggregate hasher is provided, that can compute several standard + * hash functions in parallel. It uses `br_multihash_context` and a + * procedural API. It is configured with the implementations (the vtables) + * that it should use; it will then compute all these hash functions in + * parallel, on the same input. It is meant to be used in cases when the + * hash of an object will be used, but the exact hash function is not + * known yet (typically, streamed processing on X.509 certificates). + * + * Only the standard hash functions (MD5, SHA-1, SHA-224, SHA-256, SHA-384 + * and SHA-512) are supported by the multi-hasher. + * + * + * ## GHASH + * + * GHASH is not a generic hash function; it is a _universal_ hash function, + * which, as the name does not say, means that it CANNOT be used in most + * places where a hash function is needed. GHASH is used within the GCM + * encryption mode, to provide the checked integrity functionality. + * + * A GHASH implementation is basically a function that uses the type defined + * in this file under the name `br_ghash`: + * + * typedef void (*br_ghash)(void *y, const void *h, const void *data, size_t len); + * + * The `y` pointer refers to a 16-byte value which is used as input, and + * receives the output of the GHASH invocation. `h` is a 16-byte secret + * value (that serves as key). `data` and `len` define the input data. + * + * Three GHASH implementations are provided, all constant-time, based on + * the use of integer multiplications with appropriate masking to cancel + * carry propagation. + */ + +/** + * \brief Class type for hash function implementations. + * + * A `br_hash_class` instance references the methods implementing a hash + * function. Constant instances of this structure are defined for each + * implemented hash function. Such instances are also called "vtables". + * + * Vtables are used to support object-oriented programming, as + * described on [the BearSSL Web site](https://www.bearssl.org/oop.html). + */ +typedef struct br_hash_class_ br_hash_class; +struct br_hash_class_ { + /** + * \brief Size (in bytes) of the context structure appropriate for + * computing this hash function. + */ + size_t context_size; + + /** + * \brief Descriptor word that contains information about the hash + * function. + * + * For each word `xxx` described below, use `BR_HASHDESC_xxx_OFF` + * and `BR_HASHDESC_xxx_MASK` to access the specific value, as + * follows: + * + * (hf->desc >> BR_HASHDESC_xxx_OFF) & BR_HASHDESC_xxx_MASK + * + * The defined elements are: + * + * - `ID`: the symbolic identifier for the function, as defined + * in [TLS](https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1) + * (MD5 = 1, SHA-1 = 2,...). + * + * - `OUT`: hash output size, in bytes. + * + * - `STATE`: internal running state size, in bytes. + * + * - `LBLEN`: base-2 logarithm for the internal block size, as + * defined for HMAC processing (this is 6 for MD5, SHA-1, SHA-224 + * and SHA-256, since these functions use 64-byte blocks; for + * SHA-384 and SHA-512, this is 7, corresponding to their + * 128-byte blocks). + * + * The descriptor may contain a few other flags. + */ + uint32_t desc; + + /** + * \brief Initialisation method. + * + * This method takes as parameter a pointer to a context area, + * that it initialises. The first field of the context is set + * to this vtable; other elements are initialised for a new hash + * computation. + * + * \param ctx pointer to (the first field of) the context. + */ + void (*init)(const br_hash_class **ctx); + + /** + * \brief Data injection method. + * + * The `len` bytes starting at address `data` are injected into + * the running hash computation incarnated by the specified + * context. The context is updated accordingly. It is allowed + * to have `len == 0`, in which case `data` is ignored (and could + * be `NULL`), and nothing happens. + * on the input data. + * + * \param ctx pointer to (the first field of) the context. + * \param data pointer to the first data byte to inject. + * \param len number of bytes to inject. + */ + void (*update)(const br_hash_class **ctx, const void *data, size_t len); + + /** + * \brief Produce hash output. + * + * The hash output corresponding to all data bytes injected in the + * context since the last `init()` call is computed, and written + * in the buffer pointed to by `dst`. The hash output size depends + * on the implemented hash function (e.g. 16 bytes for MD5). + * The context is _not_ modified by this call, so further bytes + * may be afterwards injected to continue the current computation. + * + * \param ctx pointer to (the first field of) the context. + * \param dst destination buffer for the hash output. + */ + void (*out)(const br_hash_class *const *ctx, void *dst); + + /** + * \brief Get running state. + * + * This method saves the current running state into the `dst` + * buffer. What constitutes the "running state" depends on the + * hash function; for Merkle-DamgÃ¥rd hash functions (like + * MD5 or SHA-1), this is the output obtained after processing + * each block. The number of bytes injected so far is returned. + * The context is not modified by this call. + * + * \param ctx pointer to (the first field of) the context. + * \param dst destination buffer for the state. + * \return the injected total byte length. + */ + uint64_t (*state)(const br_hash_class *const *ctx, void *dst); + + /** + * \brief Set running state. + * + * This methods replaces the running state for the function. + * + * \param ctx pointer to (the first field of) the context. + * \param stb source buffer for the state. + * \param count injected total byte length. + */ + void (*set_state)(const br_hash_class **ctx, + const void *stb, uint64_t count); +}; + +#ifndef BR_DOXYGEN_IGNORE +#define BR_HASHDESC_ID(id) ((uint32_t)(id) << BR_HASHDESC_ID_OFF) +#define BR_HASHDESC_ID_OFF 0 +#define BR_HASHDESC_ID_MASK 0xFF + +#define BR_HASHDESC_OUT(size) ((uint32_t)(size) << BR_HASHDESC_OUT_OFF) +#define BR_HASHDESC_OUT_OFF 8 +#define BR_HASHDESC_OUT_MASK 0x7F + +#define BR_HASHDESC_STATE(size) ((uint32_t)(size) << BR_HASHDESC_STATE_OFF) +#define BR_HASHDESC_STATE_OFF 15 +#define BR_HASHDESC_STATE_MASK 0xFF + +#define BR_HASHDESC_LBLEN(ls) ((uint32_t)(ls) << BR_HASHDESC_LBLEN_OFF) +#define BR_HASHDESC_LBLEN_OFF 23 +#define BR_HASHDESC_LBLEN_MASK 0x0F + +#define BR_HASHDESC_MD_PADDING ((uint32_t)1 << 28) +#define BR_HASHDESC_MD_PADDING_128 ((uint32_t)1 << 29) +#define BR_HASHDESC_MD_PADDING_BE ((uint32_t)1 << 30) +#endif + +/* + * Specific hash functions. + * + * Rules for contexts: + * -- No interior pointer. + * -- No pointer to external dynamically allocated resources. + * -- First field is called 'vtable' and is a pointer to a + * const-qualified br_hash_class instance (pointer is set by init()). + * -- SHA-224 and SHA-256 contexts are identical. + * -- SHA-384 and SHA-512 contexts are identical. + * + * Thus, contexts can be moved and cloned to capture the hash function + * current state; and there is no need for any explicit "release" function. + */ + +/** + * \brief Symbolic identifier for MD5. + */ +#define br_md5_ID 1 + +/** + * \brief MD5 output size (in bytes). + */ +#define br_md5_SIZE 16 + +/** + * \brief Constant vtable for MD5. + */ +extern const br_hash_class br_md5_vtable; + +/** + * \brief MD5 context. + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** + * \brief Pointer to vtable for this context. + */ + const br_hash_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + unsigned char buf[64]; + uint64_t count; + uint32_t val[4]; +#endif +} br_md5_context; + +/** + * \brief MD5 context initialisation. + * + * This function initialises or resets a context for a new MD5 + * computation. It also sets the vtable pointer. + * + * \param ctx pointer to the context structure. + */ +void br_md5_init(br_md5_context *ctx); + +/** + * \brief Inject some data bytes in a running MD5 computation. + * + * The provided context is updated with some data bytes. If the number + * of bytes (`len`) is zero, then the data pointer (`data`) is ignored + * and may be `NULL`, and this function does nothing. + * + * \param ctx pointer to the context structure. + * \param data pointer to the injected data. + * \param len injected data length (in bytes). + */ +void br_md5_update(br_md5_context *ctx, const void *data, size_t len); + +/** + * \brief Compute MD5 output. + * + * The MD5 output for the concatenation of all bytes injected in the + * provided context since the last initialisation or reset call, is + * computed and written in the buffer pointed to by `out`. The context + * itself is not modified, so extra bytes may be injected afterwards + * to continue that computation. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the hash output. + */ +void br_md5_out(const br_md5_context *ctx, void *out); + +/** + * \brief Save MD5 running state. + * + * The running state for MD5 (output of the last internal block + * processing) is written in the buffer pointed to by `out`. The + * number of bytes injected since the last initialisation or reset + * call is returned. The context is not modified. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the running state. + * \return the injected total byte length. + */ +uint64_t br_md5_state(const br_md5_context *ctx, void *out); + +/** + * \brief Restore MD5 running state. + * + * The running state for MD5 is set to the provided values. + * + * \param ctx pointer to the context structure. + * \param stb source buffer for the running state. + * \param count the injected total byte length. + */ +void br_md5_set_state(br_md5_context *ctx, const void *stb, uint64_t count); + +/** + * \brief Symbolic identifier for SHA-1. + */ +#define br_sha1_ID 2 + +/** + * \brief SHA-1 output size (in bytes). + */ +#define br_sha1_SIZE 20 + +/** + * \brief Constant vtable for SHA-1. + */ +extern const br_hash_class br_sha1_vtable; + +/** + * \brief SHA-1 context. + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** + * \brief Pointer to vtable for this context. + */ + const br_hash_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + unsigned char buf[64]; + uint64_t count; + uint32_t val[5]; +#endif +} br_sha1_context; + +/** + * \brief SHA-1 context initialisation. + * + * This function initialises or resets a context for a new SHA-1 + * computation. It also sets the vtable pointer. + * + * \param ctx pointer to the context structure. + */ +void br_sha1_init(br_sha1_context *ctx); + +/** + * \brief Inject some data bytes in a running SHA-1 computation. + * + * The provided context is updated with some data bytes. If the number + * of bytes (`len`) is zero, then the data pointer (`data`) is ignored + * and may be `NULL`, and this function does nothing. + * + * \param ctx pointer to the context structure. + * \param data pointer to the injected data. + * \param len injected data length (in bytes). + */ +void br_sha1_update(br_sha1_context *ctx, const void *data, size_t len); + +/** + * \brief Compute SHA-1 output. + * + * The SHA-1 output for the concatenation of all bytes injected in the + * provided context since the last initialisation or reset call, is + * computed and written in the buffer pointed to by `out`. The context + * itself is not modified, so extra bytes may be injected afterwards + * to continue that computation. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the hash output. + */ +void br_sha1_out(const br_sha1_context *ctx, void *out); + +/** + * \brief Save SHA-1 running state. + * + * The running state for SHA-1 (output of the last internal block + * processing) is written in the buffer pointed to by `out`. The + * number of bytes injected since the last initialisation or reset + * call is returned. The context is not modified. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the running state. + * \return the injected total byte length. + */ +uint64_t br_sha1_state(const br_sha1_context *ctx, void *out); + +/** + * \brief Restore SHA-1 running state. + * + * The running state for SHA-1 is set to the provided values. + * + * \param ctx pointer to the context structure. + * \param stb source buffer for the running state. + * \param count the injected total byte length. + */ +void br_sha1_set_state(br_sha1_context *ctx, const void *stb, uint64_t count); + +/** + * \brief Symbolic identifier for SHA-224. + */ +#define br_sha224_ID 3 + +/** + * \brief SHA-224 output size (in bytes). + */ +#define br_sha224_SIZE 28 + +/** + * \brief Constant vtable for SHA-224. + */ +extern const br_hash_class br_sha224_vtable; + +/** + * \brief SHA-224 context. + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** + * \brief Pointer to vtable for this context. + */ + const br_hash_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + unsigned char buf[64]; + uint64_t count; + uint32_t val[8]; +#endif +} br_sha224_context; + +/** + * \brief SHA-224 context initialisation. + * + * This function initialises or resets a context for a new SHA-224 + * computation. It also sets the vtable pointer. + * + * \param ctx pointer to the context structure. + */ +void br_sha224_init(br_sha224_context *ctx); + +/** + * \brief Inject some data bytes in a running SHA-224 computation. + * + * The provided context is updated with some data bytes. If the number + * of bytes (`len`) is zero, then the data pointer (`data`) is ignored + * and may be `NULL`, and this function does nothing. + * + * \param ctx pointer to the context structure. + * \param data pointer to the injected data. + * \param len injected data length (in bytes). + */ +void br_sha224_update(br_sha224_context *ctx, const void *data, size_t len); + +/** + * \brief Compute SHA-224 output. + * + * The SHA-224 output for the concatenation of all bytes injected in the + * provided context since the last initialisation or reset call, is + * computed and written in the buffer pointed to by `out`. The context + * itself is not modified, so extra bytes may be injected afterwards + * to continue that computation. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the hash output. + */ +void br_sha224_out(const br_sha224_context *ctx, void *out); + +/** + * \brief Save SHA-224 running state. + * + * The running state for SHA-224 (output of the last internal block + * processing) is written in the buffer pointed to by `out`. The + * number of bytes injected since the last initialisation or reset + * call is returned. The context is not modified. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the running state. + * \return the injected total byte length. + */ +uint64_t br_sha224_state(const br_sha224_context *ctx, void *out); + +/** + * \brief Restore SHA-224 running state. + * + * The running state for SHA-224 is set to the provided values. + * + * \param ctx pointer to the context structure. + * \param stb source buffer for the running state. + * \param count the injected total byte length. + */ +void br_sha224_set_state(br_sha224_context *ctx, + const void *stb, uint64_t count); + +/** + * \brief Symbolic identifier for SHA-256. + */ +#define br_sha256_ID 4 + +/** + * \brief SHA-256 output size (in bytes). + */ +#define br_sha256_SIZE 32 + +/** + * \brief Constant vtable for SHA-256. + */ +extern const br_hash_class br_sha256_vtable; + +#ifdef BR_DOXYGEN_IGNORE +/** + * \brief SHA-256 context. + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** + * \brief Pointer to vtable for this context. + */ + const br_hash_class *vtable; +} br_sha256_context; +#else +typedef br_sha224_context br_sha256_context; +#endif + +/** + * \brief SHA-256 context initialisation. + * + * This function initialises or resets a context for a new SHA-256 + * computation. It also sets the vtable pointer. + * + * \param ctx pointer to the context structure. + */ +void br_sha256_init(br_sha256_context *ctx); + +#ifdef BR_DOXYGEN_IGNORE +/** + * \brief Inject some data bytes in a running SHA-256 computation. + * + * The provided context is updated with some data bytes. If the number + * of bytes (`len`) is zero, then the data pointer (`data`) is ignored + * and may be `NULL`, and this function does nothing. + * + * \param ctx pointer to the context structure. + * \param data pointer to the injected data. + * \param len injected data length (in bytes). + */ +void br_sha256_update(br_sha256_context *ctx, const void *data, size_t len); +#else +#define br_sha256_update br_sha224_update +#endif + +/** + * \brief Compute SHA-256 output. + * + * The SHA-256 output for the concatenation of all bytes injected in the + * provided context since the last initialisation or reset call, is + * computed and written in the buffer pointed to by `out`. The context + * itself is not modified, so extra bytes may be injected afterwards + * to continue that computation. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the hash output. + */ +void br_sha256_out(const br_sha256_context *ctx, void *out); + +#ifdef BR_DOXYGEN_IGNORE +/** + * \brief Save SHA-256 running state. + * + * The running state for SHA-256 (output of the last internal block + * processing) is written in the buffer pointed to by `out`. The + * number of bytes injected since the last initialisation or reset + * call is returned. The context is not modified. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the running state. + * \return the injected total byte length. + */ +uint64_t br_sha256_state(const br_sha256_context *ctx, void *out); +#else +#define br_sha256_state br_sha224_state +#endif + +#ifdef BR_DOXYGEN_IGNORE +/** + * \brief Restore SHA-256 running state. + * + * The running state for SHA-256 is set to the provided values. + * + * \param ctx pointer to the context structure. + * \param stb source buffer for the running state. + * \param count the injected total byte length. + */ +void br_sha256_set_state(br_sha256_context *ctx, + const void *stb, uint64_t count); +#else +#define br_sha256_set_state br_sha224_set_state +#endif + +/** + * \brief Symbolic identifier for SHA-384. + */ +#define br_sha384_ID 5 + +/** + * \brief SHA-384 output size (in bytes). + */ +#define br_sha384_SIZE 48 + +/** + * \brief Constant vtable for SHA-384. + */ +extern const br_hash_class br_sha384_vtable; + +/** + * \brief SHA-384 context. + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** + * \brief Pointer to vtable for this context. + */ + const br_hash_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + unsigned char buf[128]; + uint64_t count; + uint64_t val[8]; +#endif +} br_sha384_context; + +/** + * \brief SHA-384 context initialisation. + * + * This function initialises or resets a context for a new SHA-384 + * computation. It also sets the vtable pointer. + * + * \param ctx pointer to the context structure. + */ +void br_sha384_init(br_sha384_context *ctx); + +/** + * \brief Inject some data bytes in a running SHA-384 computation. + * + * The provided context is updated with some data bytes. If the number + * of bytes (`len`) is zero, then the data pointer (`data`) is ignored + * and may be `NULL`, and this function does nothing. + * + * \param ctx pointer to the context structure. + * \param data pointer to the injected data. + * \param len injected data length (in bytes). + */ +void br_sha384_update(br_sha384_context *ctx, const void *data, size_t len); + +/** + * \brief Compute SHA-384 output. + * + * The SHA-384 output for the concatenation of all bytes injected in the + * provided context since the last initialisation or reset call, is + * computed and written in the buffer pointed to by `out`. The context + * itself is not modified, so extra bytes may be injected afterwards + * to continue that computation. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the hash output. + */ +void br_sha384_out(const br_sha384_context *ctx, void *out); + +/** + * \brief Save SHA-384 running state. + * + * The running state for SHA-384 (output of the last internal block + * processing) is written in the buffer pointed to by `out`. The + * number of bytes injected since the last initialisation or reset + * call is returned. The context is not modified. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the running state. + * \return the injected total byte length. + */ +uint64_t br_sha384_state(const br_sha384_context *ctx, void *out); + +/** + * \brief Restore SHA-384 running state. + * + * The running state for SHA-384 is set to the provided values. + * + * \param ctx pointer to the context structure. + * \param stb source buffer for the running state. + * \param count the injected total byte length. + */ +void br_sha384_set_state(br_sha384_context *ctx, + const void *stb, uint64_t count); + +/** + * \brief Symbolic identifier for SHA-512. + */ +#define br_sha512_ID 6 + +/** + * \brief SHA-512 output size (in bytes). + */ +#define br_sha512_SIZE 64 + +/** + * \brief Constant vtable for SHA-512. + */ +extern const br_hash_class br_sha512_vtable; + +#ifdef BR_DOXYGEN_IGNORE +/** + * \brief SHA-512 context. + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** + * \brief Pointer to vtable for this context. + */ + const br_hash_class *vtable; +} br_sha512_context; +#else +typedef br_sha384_context br_sha512_context; +#endif + +/** + * \brief SHA-512 context initialisation. + * + * This function initialises or resets a context for a new SHA-512 + * computation. It also sets the vtable pointer. + * + * \param ctx pointer to the context structure. + */ +void br_sha512_init(br_sha512_context *ctx); + +#ifdef BR_DOXYGEN_IGNORE +/** + * \brief Inject some data bytes in a running SHA-512 computation. + * + * The provided context is updated with some data bytes. If the number + * of bytes (`len`) is zero, then the data pointer (`data`) is ignored + * and may be `NULL`, and this function does nothing. + * + * \param ctx pointer to the context structure. + * \param data pointer to the injected data. + * \param len injected data length (in bytes). + */ +void br_sha512_update(br_sha512_context *ctx, const void *data, size_t len); +#else +#define br_sha512_update br_sha384_update +#endif + +/** + * \brief Compute SHA-512 output. + * + * The SHA-512 output for the concatenation of all bytes injected in the + * provided context since the last initialisation or reset call, is + * computed and written in the buffer pointed to by `out`. The context + * itself is not modified, so extra bytes may be injected afterwards + * to continue that computation. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the hash output. + */ +void br_sha512_out(const br_sha512_context *ctx, void *out); + +#ifdef BR_DOXYGEN_IGNORE +/** + * \brief Save SHA-512 running state. + * + * The running state for SHA-512 (output of the last internal block + * processing) is written in the buffer pointed to by `out`. The + * number of bytes injected since the last initialisation or reset + * call is returned. The context is not modified. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the running state. + * \return the injected total byte length. + */ +uint64_t br_sha512_state(const br_sha512_context *ctx, void *out); +#else +#define br_sha512_state br_sha384_state +#endif + +#ifdef BR_DOXYGEN_IGNORE +/** + * \brief Restore SHA-512 running state. + * + * The running state for SHA-512 is set to the provided values. + * + * \param ctx pointer to the context structure. + * \param stb source buffer for the running state. + * \param count the injected total byte length. + */ +void br_sha512_set_state(br_sha512_context *ctx, + const void *stb, uint64_t count); +#else +#define br_sha512_set_state br_sha384_set_state +#endif + +/* + * "md5sha1" is a special hash function that computes both MD5 and SHA-1 + * on the same input, and produces a 36-byte output (MD5 and SHA-1 + * concatenation, in that order). State size is also 36 bytes. + */ + +/** + * \brief Symbolic identifier for MD5+SHA-1. + * + * MD5+SHA-1 is the concatenation of MD5 and SHA-1, computed over the + * same input. It is not one of the functions identified in TLS, so + * we give it a symbolic identifier of value 0. + */ +#define br_md5sha1_ID 0 + +/** + * \brief MD5+SHA-1 output size (in bytes). + */ +#define br_md5sha1_SIZE 36 + +/** + * \brief Constant vtable for MD5+SHA-1. + */ +extern const br_hash_class br_md5sha1_vtable; + +/** + * \brief MD5+SHA-1 context. + * + * First field is a pointer to the vtable; it is set by the initialisation + * function. Other fields are not supposed to be accessed by user code. + */ +typedef struct { + /** + * \brief Pointer to vtable for this context. + */ + const br_hash_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + unsigned char buf[64]; + uint64_t count; + uint32_t val_md5[4]; + uint32_t val_sha1[5]; +#endif +} br_md5sha1_context; + +/** + * \brief MD5+SHA-1 context initialisation. + * + * This function initialises or resets a context for a new SHA-512 + * computation. It also sets the vtable pointer. + * + * \param ctx pointer to the context structure. + */ +void br_md5sha1_init(br_md5sha1_context *ctx); + +/** + * \brief Inject some data bytes in a running MD5+SHA-1 computation. + * + * The provided context is updated with some data bytes. If the number + * of bytes (`len`) is zero, then the data pointer (`data`) is ignored + * and may be `NULL`, and this function does nothing. + * + * \param ctx pointer to the context structure. + * \param data pointer to the injected data. + * \param len injected data length (in bytes). + */ +void br_md5sha1_update(br_md5sha1_context *ctx, const void *data, size_t len); + +/** + * \brief Compute MD5+SHA-1 output. + * + * The MD5+SHA-1 output for the concatenation of all bytes injected in the + * provided context since the last initialisation or reset call, is + * computed and written in the buffer pointed to by `out`. The context + * itself is not modified, so extra bytes may be injected afterwards + * to continue that computation. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the hash output. + */ +void br_md5sha1_out(const br_md5sha1_context *ctx, void *out); + +/** + * \brief Save MD5+SHA-1 running state. + * + * The running state for MD5+SHA-1 (output of the last internal block + * processing) is written in the buffer pointed to by `out`. The + * number of bytes injected since the last initialisation or reset + * call is returned. The context is not modified. + * + * \param ctx pointer to the context structure. + * \param out destination buffer for the running state. + * \return the injected total byte length. + */ +uint64_t br_md5sha1_state(const br_md5sha1_context *ctx, void *out); + +/** + * \brief Restore MD5+SHA-1 running state. + * + * The running state for MD5+SHA-1 is set to the provided values. + * + * \param ctx pointer to the context structure. + * \param stb source buffer for the running state. + * \param count the injected total byte length. + */ +void br_md5sha1_set_state(br_md5sha1_context *ctx, + const void *stb, uint64_t count); + +/** + * \brief Aggregate context for configurable hash function support. + * + * The `br_hash_compat_context` type is a type which is large enough to + * serve as context for all standard hash functions defined above. + */ +typedef union { + const br_hash_class *vtable; + br_md5_context md5; + br_sha1_context sha1; + br_sha224_context sha224; + br_sha256_context sha256; + br_sha384_context sha384; + br_sha512_context sha512; + br_md5sha1_context md5sha1; +} br_hash_compat_context; + +/* + * The multi-hasher is a construct that handles hashing of the same input + * data with several hash functions, with a single shared input buffer. + * It can handle MD5, SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 + * simultaneously, though which functions are activated depends on + * the set implementation pointers. + */ + +/** + * \brief Multi-hasher context structure. + * + * The multi-hasher runs up to six hash functions in the standard TLS list + * (MD5, SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512) in parallel, over + * the same input. + * + * The multi-hasher does _not_ follow the OOP structure with a vtable. + * Instead, it is configured with the vtables of the hash functions it + * should run. Structure fields are not supposed to be accessed directly. + */ +typedef struct { +#ifndef BR_DOXYGEN_IGNORE + unsigned char buf[128]; + uint64_t count; + uint32_t val_32[25]; + uint64_t val_64[16]; + const br_hash_class *impl[6]; +#endif +} br_multihash_context; + +/** + * \brief Clear a multi-hasher context. + * + * This should always be called once on a given context, _before_ setting + * the implementation pointers. + * + * \param ctx the multi-hasher context. + */ +void br_multihash_zero(br_multihash_context *ctx); + +/** + * \brief Set a hash function implementation. + * + * Implementations shall be set _after_ clearing the context (with + * `br_multihash_zero()`) but _before_ initialising the computation + * (with `br_multihash_init()`). The hash function implementation + * MUST be one of the standard hash functions (MD5, SHA-1, SHA-224, + * SHA-256, SHA-384 or SHA-512); it may also be `NULL` to remove + * an implementation from the multi-hasher. + * + * \param ctx the multi-hasher context. + * \param id the hash function symbolic identifier. + * \param impl the hash function vtable, or `NULL`. + */ +static inline void +br_multihash_setimpl(br_multihash_context *ctx, + int id, const br_hash_class *impl) +{ + /* + * This code relies on hash functions ID being values 1 to 6, + * in the MD5 to SHA-512 order. + */ + ctx->impl[id - 1] = impl; +} + +/** + * \brief Get a hash function implementation. + * + * This function returns the currently configured vtable for a given + * hash function (by symbolic ID). If no such function was configured in + * the provided multi-hasher context, then this function returns `NULL`. + * + * \param ctx the multi-hasher context. + * \param id the hash function symbolic identifier. + * \return the hash function vtable, or `NULL`. + */ +static inline const br_hash_class * +br_multihash_getimpl(const br_multihash_context *ctx, int id) +{ + return ctx->impl[id - 1]; +} + +/** + * \brief Reset a multi-hasher context. + * + * This function prepares the context for a new hashing computation, + * for all implementations configured at that point. + * + * \param ctx the multi-hasher context. + */ +void br_multihash_init(br_multihash_context *ctx); + +/** + * \brief Inject some data bytes in a running multi-hashing computation. + * + * The provided context is updated with some data bytes. If the number + * of bytes (`len`) is zero, then the data pointer (`data`) is ignored + * and may be `NULL`, and this function does nothing. + * + * \param ctx pointer to the context structure. + * \param data pointer to the injected data. + * \param len injected data length (in bytes). + */ +void br_multihash_update(br_multihash_context *ctx, + const void *data, size_t len); + +/** + * \brief Compute a hash output from a multi-hasher. + * + * The hash output for the concatenation of all bytes injected in the + * provided context since the last initialisation or reset call, is + * computed and written in the buffer pointed to by `dst`. The hash + * function to use is identified by `id` and must be one of the standard + * hash functions. If that hash function was indeed configured in the + * multi-hasher context, the corresponding hash value is written in + * `dst` and its length (in bytes) is returned. If the hash function + * was _not_ configured, then nothing is written in `dst` and 0 is + * returned. + * + * The context itself is not modified, so extra bytes may be injected + * afterwards to continue the hash computations. + * + * \param ctx pointer to the context structure. + * \param id the hash function symbolic identifier. + * \param dst destination buffer for the hash output. + * \return the hash output length (in bytes), or 0. + */ +size_t br_multihash_out(const br_multihash_context *ctx, int id, void *dst); + +/** + * \brief Type for a GHASH implementation. + * + * GHASH is a sort of keyed hash meant to be used to implement GCM in + * combination with a block cipher (with 16-byte blocks). + * + * The `y` array has length 16 bytes and is used for input and output; in + * a complete GHASH run, it starts with an all-zero value. `h` is a 16-byte + * value that serves as key (it is derived from the encryption key in GCM, + * using the block cipher). The data length (`len`) is expressed in bytes. + * The `y` array is updated. + * + * If the data length is not a multiple of 16, then the data is implicitly + * padded with zeros up to the next multiple of 16. Thus, when using GHASH + * in GCM, this method may be called twice, for the associated data and + * for the ciphertext, respectively; the zero-padding implements exactly + * the GCM rules. + * + * \param y the array to update. + * \param h the GHASH key. + * \param data the input data (may be `NULL` if `len` is zero). + * \param len the input data length (in bytes). + */ +typedef void (*br_ghash)(void *y, const void *h, const void *data, size_t len); + +/** + * \brief GHASH implementation using multiplications (mixed 32-bit). + * + * This implementation uses multiplications of 32-bit values, with a + * 64-bit result. It is constant-time (if multiplications are + * constant-time). + * + * \param y the array to update. + * \param h the GHASH key. + * \param data the input data (may be `NULL` if `len` is zero). + * \param len the input data length (in bytes). + */ +void br_ghash_ctmul(void *y, const void *h, const void *data, size_t len); + +/** + * \brief GHASH implementation using multiplications (strict 32-bit). + * + * This implementation uses multiplications of 32-bit values, with a + * 32-bit result. It is usually somewhat slower than `br_ghash_ctmul()`, + * but it is expected to be faster on architectures for which the + * 32-bit multiplication opcode does not yield the upper 32 bits of the + * product. It is constant-time (if multiplications are constant-time). + * + * \param y the array to update. + * \param h the GHASH key. + * \param data the input data (may be `NULL` if `len` is zero). + * \param len the input data length (in bytes). + */ +void br_ghash_ctmul32(void *y, const void *h, const void *data, size_t len); + +/** + * \brief GHASH implementation using multiplications (64-bit). + * + * This implementation uses multiplications of 64-bit values, with a + * 64-bit result. It is constant-time (if multiplications are + * constant-time). It is substantially faster than `br_ghash_ctmul()` + * and `br_ghash_ctmul32()` on most 64-bit architectures. + * + * \param y the array to update. + * \param h the GHASH key. + * \param data the input data (may be `NULL` if `len` is zero). + * \param len the input data length (in bytes). + */ +void br_ghash_ctmul64(void *y, const void *h, const void *data, size_t len); + +/** + * \brief GHASH implementation using the `pclmulqdq` opcode (part of the + * AES-NI instructions). + * + * This implementation is available only on x86 platforms where the + * compiler supports the relevant intrinsic functions. Even if the + * compiler supports these functions, the local CPU might not support + * the `pclmulqdq` opcode, meaning that a call will fail with an + * illegal instruction exception. To safely obtain a pointer to this + * function when supported (or 0 otherwise), use `br_ghash_pclmul_get()`. + * + * \param y the array to update. + * \param h the GHASH key. + * \param data the input data (may be `NULL` if `len` is zero). + * \param len the input data length (in bytes). + */ +void br_ghash_pclmul(void *y, const void *h, const void *data, size_t len); + +/** + * \brief Obtain the `pclmul` GHASH implementation, if available. + * + * If the `pclmul` implementation was compiled in the library (depending + * on the compiler abilities) _and_ the local CPU appears to support the + * opcode, then this function will return a pointer to the + * `br_ghash_pclmul()` function. Otherwise, it will return `0`. + * + * \return the `pclmul` GHASH implementation, or `0`. + */ +br_ghash br_ghash_pclmul_get(void); + +/** + * \brief GHASH implementation using the POWER8 opcodes. + * + * This implementation is available only on POWER8 platforms (and later). + * To safely obtain a pointer to this function when supported (or 0 + * otherwise), use `br_ghash_pwr8_get()`. + * + * \param y the array to update. + * \param h the GHASH key. + * \param data the input data (may be `NULL` if `len` is zero). + * \param len the input data length (in bytes). + */ +void br_ghash_pwr8(void *y, const void *h, const void *data, size_t len); + +/** + * \brief Obtain the `pwr8` GHASH implementation, if available. + * + * If the `pwr8` implementation was compiled in the library (depending + * on the compiler abilities) _and_ the local CPU appears to support the + * opcode, then this function will return a pointer to the + * `br_ghash_pwr8()` function. Otherwise, it will return `0`. + * + * \return the `pwr8` GHASH implementation, or `0`. + */ +br_ghash br_ghash_pwr8_get(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/enclave/include/bearssl/bearssl_hmac.h b/enclave/include/bearssl/bearssl_hmac.h new file mode 100644 index 0000000..4dc01ca --- /dev/null +++ b/enclave/include/bearssl/bearssl_hmac.h @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_HMAC_H__ +#define BR_BEARSSL_HMAC_H__ + +#include +#include + +#include "bearssl_hash.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file bearssl_hmac.h + * + * # HMAC + * + * HMAC is initialized with a key and an underlying hash function; it + * then fills a "key context". That context contains the processed + * key. + * + * With the key context, a HMAC context can be initialized to process + * the input bytes and obtain the MAC output. The key context is not + * modified during that process, and can be reused. + * + * IMPORTANT: HMAC shall be used only with functions that have the + * following properties: + * + * - hash output size does not exceed 64 bytes; + * - hash internal state size does not exceed 64 bytes; + * - internal block length is a power of 2 between 16 and 256 bytes. + */ + +/** + * \brief HMAC key context. + * + * The HMAC key context is initialised with a hash function implementation + * and a secret key. Contents are opaque (callers should not access them + * directly). The caller is responsible for allocating the context where + * appropriate. Context initialisation and usage incurs no dynamic + * allocation, so there is no release function. + */ +typedef struct { +#ifndef BR_DOXYGEN_IGNORE + const br_hash_class *dig_vtable; + unsigned char ksi[64], kso[64]; +#endif +} br_hmac_key_context; + +/** + * \brief HMAC key context initialisation. + * + * Initialise the key context with the provided key, using the hash function + * identified by `digest_vtable`. This supports arbitrary key lengths. + * + * \param kc HMAC key context to initialise. + * \param digest_vtable pointer to the hash function implementation vtable. + * \param key pointer to the HMAC secret key. + * \param key_len HMAC secret key length (in bytes). + */ +void br_hmac_key_init(br_hmac_key_context *kc, + const br_hash_class *digest_vtable, const void *key, size_t key_len); + +/* + * \brief Get the underlying hash function. + * + * This function returns a pointer to the implementation vtable of the + * hash function used for this HMAC key context. + * + * \param kc HMAC key context. + * \return the hash function implementation. + */ +static inline const br_hash_class *br_hmac_key_get_digest( + const br_hmac_key_context *kc) +{ + return kc->dig_vtable; +} + +/** + * \brief HMAC computation context. + * + * The HMAC computation context maintains the state for a single HMAC + * computation. It is modified as input bytes are injected. The context + * is caller-allocated and has no release function since it does not + * dynamically allocate external resources. Its contents are opaque. + */ +typedef struct { +#ifndef BR_DOXYGEN_IGNORE + br_hash_compat_context dig; + unsigned char kso[64]; + size_t out_len; +#endif +} br_hmac_context; + +/** + * \brief HMAC computation initialisation. + * + * Initialise a HMAC context with a key context. The key context is + * unmodified. Relevant data from the key context is immediately copied; + * the key context can thus be independently reused, modified or released + * without impacting this HMAC computation. + * + * An explicit output length can be specified; the actual output length + * will be the minimum of that value and the natural HMAC output length. + * If `out_len` is 0, then the natural HMAC output length is selected. The + * "natural output length" is the output length of the underlying hash + * function. + * + * \param ctx HMAC context to initialise. + * \param kc HMAC key context (already initialised with the key). + * \param out_len HMAC output length (0 to select "natural length"). + */ +void br_hmac_init(br_hmac_context *ctx, + const br_hmac_key_context *kc, size_t out_len); + +/** + * \brief Get the HMAC output size. + * + * The HMAC output size is the number of bytes that will actually be + * produced with `br_hmac_out()` with the provided context. This function + * MUST NOT be called on a non-initialised HMAC computation context. + * The returned value is the minimum of the HMAC natural length (output + * size of the underlying hash function) and the `out_len` parameter which + * was used with the last `br_hmac_init()` call on that context (if the + * initialisation `out_len` parameter was 0, then this function will + * return the HMAC natural length). + * + * \param ctx the (already initialised) HMAC computation context. + * \return the HMAC actual output size. + */ +static inline size_t +br_hmac_size(br_hmac_context *ctx) +{ + return ctx->out_len; +} + +/* + * \brief Get the underlying hash function. + * + * This function returns a pointer to the implementation vtable of the + * hash function used for this HMAC context. + * + * \param hc HMAC context. + * \return the hash function implementation. + */ +static inline const br_hash_class *br_hmac_get_digest( + const br_hmac_context *hc) +{ + return hc->dig.vtable; +} + +/** + * \brief Inject some bytes in HMAC. + * + * The provided `len` bytes are injected as extra input in the HMAC + * computation incarnated by the `ctx` HMAC context. It is acceptable + * that `len` is zero, in which case `data` is ignored (and may be + * `NULL`) and this function does nothing. + */ +void br_hmac_update(br_hmac_context *ctx, const void *data, size_t len); + +/** + * \brief Compute the HMAC output. + * + * The destination buffer MUST be large enough to accommodate the result; + * its length is at most the "natural length" of HMAC (i.e. the output + * length of the underlying hash function). The context is NOT modified; + * further bytes may be processed. Thus, "partial HMAC" values can be + * efficiently obtained. + * + * Returned value is the output length (in bytes). + * + * \param ctx HMAC computation context. + * \param out destination buffer for the HMAC output. + * \return the produced value length (in bytes). + */ +size_t br_hmac_out(const br_hmac_context *ctx, void *out); + +/** + * \brief Constant-time HMAC computation. + * + * This function compute the HMAC output in constant time. Some extra + * input bytes are processed, then the output is computed. The extra + * input consists in the `len` bytes pointed to by `data`. The `len` + * parameter must lie between `min_len` and `max_len` (inclusive); + * `max_len` bytes are actually read from `data`. Computing time (and + * memory access pattern) will not depend upon the data byte contents or + * the value of `len`. + * + * The output is written in the `out` buffer, that MUST be large enough + * to receive it. + * + * The difference `max_len - min_len` MUST be less than 230 + * (i.e. about one gigabyte). + * + * This function computes the output properly only if the underlying + * hash function uses MD padding (i.e. MD5, SHA-1, SHA-224, SHA-256, + * SHA-384 or SHA-512). + * + * The provided context is NOT modified. + * + * \param ctx the (already initialised) HMAC computation context. + * \param data the extra input bytes. + * \param len the extra input length (in bytes). + * \param min_len minimum extra input length (in bytes). + * \param max_len maximum extra input length (in bytes). + * \param out destination buffer for the HMAC output. + * \return the produced value length (in bytes). + */ +size_t br_hmac_outCT(const br_hmac_context *ctx, + const void *data, size_t len, size_t min_len, size_t max_len, + void *out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/enclave/include/bearssl/bearssl_kdf.h b/enclave/include/bearssl/bearssl_kdf.h new file mode 100644 index 0000000..955b843 --- /dev/null +++ b/enclave/include/bearssl/bearssl_kdf.h @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2018 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_KDF_H__ +#define BR_BEARSSL_KDF_H__ + +#include +#include + +#include "bearssl_hash.h" +#include "bearssl_hmac.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file bearssl_kdf.h + * + * # Key Derivation Functions + * + * KDF are functions that takes a variable length input, and provide a + * variable length output, meant to be used to derive subkeys from a + * master key. + * + * ## HKDF + * + * HKDF is a KDF defined by [RFC 5869](https://tools.ietf.org/html/rfc5869). + * It is based on HMAC, itself using an underlying hash function. Any + * hash function can be used, as long as it is compatible with the rules + * for the HMAC implementation (i.e. output size is 64 bytes or less, hash + * internal state size is 64 bytes or less, and the internal block length is + * a power of 2 between 16 and 256 bytes). HKDF has two phases: + * + * - HKDF-Extract: the input data in ingested, along with a "salt" value. + * + * - HKDF-Expand: the output is produced, from the result of processing + * the input and salt, and using an extra non-secret parameter called + * "info". + * + * The "salt" and "info" strings are non-secret and can be empty. Their role + * is normally to bind the input and output, respectively, to conventional + * identifiers that qualifu them within the used protocol or application. + * + * The implementation defined in this file uses the following functions: + * + * - `br_hkdf_init()`: initialize an HKDF context, with a hash function, + * and the salt. This starts the HKDF-Extract process. + * + * - `br_hkdf_inject()`: inject more input bytes. This function may be + * called repeatedly if the input data is provided by chunks. + * + * - `br_hkdf_flip()`: end the HKDF-Extract process, and start the + * HKDF-Expand process. + * + * - `br_hkdf_produce()`: get the next bytes of output. This function + * may be called several times to obtain the full output by chunks. + * For correct HKDF processing, the same "info" string must be + * provided for each call. + * + * Note that the HKDF total output size (the number of bytes that + * HKDF-Expand is willing to produce) is limited: if the hash output size + * is _n_ bytes, then the maximum output size is _255*n_. + * + * ## SHAKE + * + * SHAKE is defined in + * [FIPS 202](https://csrc.nist.gov/publications/detail/fips/202/final) + * under two versions: SHAKE128 and SHAKE256, offering an alleged + * "security level" of 128 and 256 bits, respectively (SHAKE128 is + * about 20 to 25% faster than SHAKE256). SHAKE internally relies on + * the Keccak family of sponge functions, not on any externally provided + * hash function. Contrary to HKDF, SHAKE does not have a concept of + * either a "salt" or an "info" string. The API consists in four + * functions: + * + * - `br_shake_init()`: initialize a SHAKE context for a given + * security level. + * + * - `br_shake_inject()`: inject more input bytes. This function may be + * called repeatedly if the input data is provided by chunks. + * + * - `br_shake_flip()`: end the data injection process, and start the + * data production process. + * + * - `br_shake_produce()`: get the next bytes of output. This function + * may be called several times to obtain the full output by chunks. + */ + +/** + * \brief HKDF context. + * + * The HKDF context is initialized with a hash function implementation + * and a salt value. Contents are opaque (callers should not access them + * directly). The caller is responsible for allocating the context where + * appropriate. Context initialisation and usage incurs no dynamic + * allocation, so there is no release function. + */ +typedef struct { +#ifndef BR_DOXYGEN_IGNORE + union { + br_hmac_context hmac_ctx; + br_hmac_key_context prk_ctx; + } u; + unsigned char buf[64]; + size_t ptr; + size_t dig_len; + unsigned chunk_num; +#endif +} br_hkdf_context; + +/** + * \brief HKDF context initialization. + * + * The underlying hash function and salt value are provided. Arbitrary + * salt lengths can be used. + * + * HKDF makes a difference between a salt of length zero, and an + * absent salt (the latter being equivalent to a salt consisting of + * bytes of value zero, of the same length as the hash function output). + * If `salt_len` is zero, then this function assumes that the salt is + * present but of length zero. To specify an _absent_ salt, use + * `BR_HKDF_NO_SALT` as `salt` parameter (`salt_len` is then ignored). + * + * \param hc HKDF context to initialise. + * \param digest_vtable pointer to the hash function implementation vtable. + * \param salt HKDF-Extract salt. + * \param salt_len HKDF-Extract salt length (in bytes). + */ +void br_hkdf_init(br_hkdf_context *hc, const br_hash_class *digest_vtable, + const void *salt, size_t salt_len); + +/** + * \brief The special "absent salt" value for HKDF. + */ +#define BR_HKDF_NO_SALT (&br_hkdf_no_salt) + +#ifndef BR_DOXYGEN_IGNORE +extern const unsigned char br_hkdf_no_salt; +#endif + +/** + * \brief HKDF input injection (HKDF-Extract). + * + * This function injects some more input bytes ("key material") into + * HKDF. This function may be called several times, after `br_hkdf_init()` + * but before `br_hkdf_flip()`. + * + * \param hc HKDF context. + * \param ikm extra input bytes. + * \param ikm_len number of extra input bytes. + */ +void br_hkdf_inject(br_hkdf_context *hc, const void *ikm, size_t ikm_len); + +/** + * \brief HKDF switch to the HKDF-Expand phase. + * + * This call terminates the HKDF-Extract process (input injection), and + * starts the HKDF-Expand process (output production). + * + * \param hc HKDF context. + */ +void br_hkdf_flip(br_hkdf_context *hc); + +/** + * \brief HKDF output production (HKDF-Expand). + * + * Produce more output bytes from the current state. This function may be + * called several times, but only after `br_hkdf_flip()`. + * + * Returned value is the number of actually produced bytes. The total + * output length is limited to 255 times the output length of the + * underlying hash function. + * + * \param hc HKDF context. + * \param info application specific information string. + * \param info_len application specific information string length (in bytes). + * \param out destination buffer for the HKDF output. + * \param out_len the length of the requested output (in bytes). + * \return the produced output length (in bytes). + */ +size_t br_hkdf_produce(br_hkdf_context *hc, + const void *info, size_t info_len, void *out, size_t out_len); + +/** + * \brief SHAKE context. + * + * The HKDF context is initialized with a "security level". The internal + * notion is called "capacity"; the capacity is twice the security level + * (for instance, SHAKE128 has capacity 256). + * + * The caller is responsible for allocating the context where + * appropriate. Context initialisation and usage incurs no dynamic + * allocation, so there is no release function. + */ +typedef struct { +#ifndef BR_DOXYGEN_IGNORE + unsigned char dbuf[200]; + size_t dptr; + size_t rate; + uint64_t A[25]; +#endif +} br_shake_context; + +/** + * \brief SHAKE context initialization. + * + * The context is initialized for the provided "security level". + * Internally, this sets the "capacity" to twice the security level; + * thus, for SHAKE128, the `security_level` parameter should be 128, + * which corresponds to a 256-bit capacity. + * + * Allowed security levels are all multiples of 32, from 32 to 768, + * inclusive. Larger security levels imply lower performance; levels + * beyond 256 bits don't make much sense. Standard levels are 128 + * and 256 bits (for SHAKE128 and SHAKE256, respectively). + * + * \param sc SHAKE context to initialise. + * \param security_level security level (in bits). + */ +void br_shake_init(br_shake_context *sc, int security_level); + +/** + * \brief SHAKE input injection. + * + * This function injects some more input bytes ("key material") into + * SHAKE. This function may be called several times, after `br_shake_init()` + * but before `br_shake_flip()`. + * + * \param sc SHAKE context. + * \param data extra input bytes. + * \param len number of extra input bytes. + */ +void br_shake_inject(br_shake_context *sc, const void *data, size_t len); + +/** + * \brief SHAKE switch to production phase. + * + * This call terminates the input injection process, and starts the + * output production process. + * + * \param sc SHAKE context. + */ +void br_shake_flip(br_shake_context *hc); + +/** + * \brief SHAKE output production. + * + * Produce more output bytes from the current state. This function may be + * called several times, but only after `br_shake_flip()`. + * + * There is no practical limit to the number of bytes that may be produced. + * + * \param sc SHAKE context. + * \param out destination buffer for the SHAKE output. + * \param len the length of the requested output (in bytes). + */ +void br_shake_produce(br_shake_context *sc, void *out, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/enclave/include/bearssl/bearssl_pem.h b/enclave/include/bearssl/bearssl_pem.h new file mode 100644 index 0000000..8dba582 --- /dev/null +++ b/enclave/include/bearssl/bearssl_pem.h @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_PEM_H__ +#define BR_BEARSSL_PEM_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file bearssl_pem.h + * + * # PEM Support + * + * PEM is a traditional encoding layer use to store binary objects (in + * particular X.509 certificates, and private keys) in text files. While + * the acronym comes from an old, defunct standard ("Privacy Enhanced + * Mail"), the format has been reused, with some variations, by many + * systems, and is a _de facto_ standard, even though it is not, actually, + * specified in all clarity anywhere. + * + * ## Format Details + * + * BearSSL contains a generic, streamed PEM decoder, which handles the + * following format: + * + * - The input source (a sequence of bytes) is assumed to be the + * encoding of a text file in an ASCII-compatible charset. This + * includes ISO-8859-1, Windows-1252, and UTF-8 encodings. Each + * line ends on a newline character (U+000A LINE FEED). The + * U+000D CARRIAGE RETURN characters are ignored, so the code + * accepts both Windows-style and Unix-style line endings. + * + * - Each object begins with a banner that occurs at the start of + * a line; the first banner characters are "`-----BEGIN `" (five + * dashes, the word "BEGIN", and a space). The banner matching is + * not case-sensitive. + * + * - The _object name_ consists in the characters that follow the + * banner start sequence, up to the end of the line, but without + * trailing dashes (in "normal" PEM, there are five trailing + * dashes, but this implementation is not picky about these dashes). + * The BearSSL decoder normalises the name characters to uppercase + * (for ASCII letters only) and accepts names up to 127 characters. + * + * - The object ends with a banner that again occurs at the start of + * a line, and starts with "`-----END `" (again case-insensitive). + * + * - Between that start and end banner, only Base64 data shall occur. + * Base64 converts each sequence of three bytes into four + * characters; the four characters are ASCII letters, digits, "`+`" + * or "`-`" signs, and one or two "`=`" signs may occur in the last + * quartet. Whitespace is ignored (whitespace is any ASCII character + * of code 32 or less, so control characters are whitespace) and + * lines may have arbitrary length; the only restriction is that the + * four characters of a quartet must appear on the same line (no + * line break inside a quartet). + * + * - A single file may contain more than one PEM object. Bytes that + * occur between objects are ignored. + * + * + * ## PEM Decoder API + * + * The PEM decoder offers a state-machine API. The caller allocates a + * decoder context, then injects source bytes. Source bytes are pushed + * with `br_pem_decoder_push()`. The decoder stops accepting bytes when + * it reaches an "event", which is either the start of an object, the + * end of an object, or a decoding error within an object. + * + * The `br_pem_decoder_event()` function is used to obtain the current + * event; it also clears it, thus allowing the decoder to accept more + * bytes. When a object start event is raised, the decoder context + * offers the found object name (normalised to ASCII uppercase). + * + * When an object is reached, the caller must set an appropriate callback + * function, which will receive (by chunks) the decoded object data. + * + * Since the decoder context makes no dynamic allocation, it requires + * no explicit deallocation. + */ + +/** + * \brief PEM decoder context. + * + * Contents are opaque (they should not be accessed directly). + */ +typedef struct { +#ifndef BR_DOXYGEN_IGNORE + /* CPU for the T0 virtual machine. */ + struct { + uint32_t *dp; + uint32_t *rp; + const unsigned char *ip; + } cpu; + uint32_t dp_stack[32]; + uint32_t rp_stack[32]; + int err; + + const unsigned char *hbuf; + size_t hlen; + + void (*dest)(void *dest_ctx, const void *src, size_t len); + void *dest_ctx; + + unsigned char event; + char name[128]; + unsigned char buf[255]; + size_t ptr; +#endif +} br_pem_decoder_context; + +/** + * \brief Initialise a PEM decoder structure. + * + * \param ctx decoder context to initialise. + */ +void br_pem_decoder_init(br_pem_decoder_context *ctx); + +/** + * \brief Push some bytes into the decoder. + * + * Returned value is the number of bytes actually consumed; this may be + * less than the number of provided bytes if an event is raised. When an + * event is raised, it must be read (with `br_pem_decoder_event()`); + * until the event is read, this function will return 0. + * + * \param ctx decoder context. + * \param data new data bytes. + * \param len number of new data bytes. + * \return the number of bytes actually received (may be less than `len`). + */ +size_t br_pem_decoder_push(br_pem_decoder_context *ctx, + const void *data, size_t len); + +/** + * \brief Set the receiver for decoded data. + * + * When an object is entered, the provided function (with opaque context + * pointer) will be called repeatedly with successive chunks of decoded + * data for that object. If `dest` is set to 0, then decoded data is + * simply ignored. The receiver can be set at any time, but, in practice, + * it should be called immediately after receiving a "start of object" + * event. + * + * \param ctx decoder context. + * \param dest callback for receiving decoded data. + * \param dest_ctx opaque context pointer for the `dest` callback. + */ +static inline void +br_pem_decoder_setdest(br_pem_decoder_context *ctx, + void (*dest)(void *dest_ctx, const void *src, size_t len), + void *dest_ctx) +{ + ctx->dest = dest; + ctx->dest_ctx = dest_ctx; +} + +/** + * \brief Get the last event. + * + * If an event was raised, then this function returns the event value, and + * also clears it, thereby allowing the decoder to proceed. If no event + * was raised since the last call to `br_pem_decoder_event()`, then this + * function returns 0. + * + * \param ctx decoder context. + * \return the raised event, or 0. + */ +int br_pem_decoder_event(br_pem_decoder_context *ctx); + +/** + * \brief Event: start of object. + * + * This event is raised when the start of a new object has been detected. + * The object name (normalised to uppercase) can be accessed with + * `br_pem_decoder_name()`. + */ +#define BR_PEM_BEGIN_OBJ 1 + +/** + * \brief Event: end of object. + * + * This event is raised when the end of the current object is reached + * (normally, i.e. with no decoding error). + */ +#define BR_PEM_END_OBJ 2 + +/** + * \brief Event: decoding error. + * + * This event is raised when decoding fails within an object. + * This formally closes the current object and brings the decoder back + * to the "out of any object" state. The offending line in the source + * is consumed. + */ +#define BR_PEM_ERROR 3 + +/** + * \brief Get the name of the encountered object. + * + * The encountered object name is defined only when the "start of object" + * event is raised. That name is normalised to uppercase (for ASCII letters + * only) and does not include trailing dashes. + * + * \param ctx decoder context. + * \return the current object name. + */ +static inline const char * +br_pem_decoder_name(br_pem_decoder_context *ctx) +{ + return ctx->name; +} + +/** + * \brief Encode an object in PEM. + * + * This function encodes the provided binary object (`data`, of length `len` + * bytes) into PEM. The `banner` text will be included in the header and + * footer (e.g. use `"CERTIFICATE"` to get a `"BEGIN CERTIFICATE"` header). + * + * The length (in characters) of the PEM output is returned; that length + * does NOT include the terminating zero, that this function nevertheless + * adds. If using the returned value for allocation purposes, the allocated + * buffer size MUST be at least one byte larger than the returned size. + * + * If `dest` is `NULL`, then the encoding does not happen; however, the + * length of the encoded object is still computed and returned. + * + * The `data` pointer may be `NULL` only if `len` is zero (when encoding + * an object of length zero, which is not very useful), or when `dest` + * is `NULL` (in that case, source data bytes are ignored). + * + * Some `flags` can be specified to alter the encoding behaviour: + * + * - If `BR_PEM_LINE64` is set, then line-breaking will occur after + * every 64 characters of output, instead of the default of 76. + * + * - If `BR_PEM_CRLF` is set, then end-of-line sequence will use + * CR+LF instead of a single LF. + * + * The `data` and `dest` buffers may overlap, in which case the source + * binary data is destroyed in the process. Note that the PEM-encoded output + * is always larger than the source binary. + * + * \param dest the destination buffer (or `NULL`). + * \param data the source buffer (can be `NULL` in some cases). + * \param len the source length (in bytes). + * \param banner the PEM banner expression. + * \param flags the behavioural flags. + * \return the PEM object length (in characters), EXCLUDING the final zero. + */ +size_t br_pem_encode(void *dest, const void *data, size_t len, + const char *banner, unsigned flags); + +/** + * \brief PEM encoding flag: split lines at 64 characters. + */ +#define BR_PEM_LINE64 0x0001 + +/** + * \brief PEM encoding flag: use CR+LF line endings. + */ +#define BR_PEM_CRLF 0x0002 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/enclave/include/bearssl/bearssl_prf.h b/enclave/include/bearssl/bearssl_prf.h new file mode 100644 index 0000000..fdf608c --- /dev/null +++ b/enclave/include/bearssl/bearssl_prf.h @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_PRF_H__ +#define BR_BEARSSL_PRF_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file bearssl_prf.h + * + * # The TLS PRF + * + * The "PRF" is the pseudorandom function used internally during the + * SSL/TLS handshake, notably to expand negotiated shared secrets into + * the symmetric encryption keys that will be used to process the + * application data. + * + * TLS 1.0 and 1.1 define a PRF that is based on both MD5 and SHA-1. This + * is implemented by the `br_tls10_prf()` function. + * + * TLS 1.2 redefines the PRF, using an explicit hash function. The + * `br_tls12_sha256_prf()` and `br_tls12_sha384_prf()` functions apply that + * PRF with, respectively, SHA-256 and SHA-384. Most standard cipher suites + * rely on the SHA-256 based PRF, but some use SHA-384. + * + * The PRF always uses as input three parameters: a "secret" (some + * bytes), a "label" (ASCII string), and a "seed" (again some bytes). An + * arbitrary output length can be produced. The "seed" is provided as an + * arbitrary number of binary chunks, that gets internally concatenated. + */ + +/** + * \brief Type for a seed chunk. + * + * Each chunk may have an arbitrary length, and may be empty (no byte at + * all). If the chunk length is zero, then the pointer to the chunk data + * may be `NULL`. + */ +typedef struct { + /** + * \brief Pointer to the chunk data. + */ + const void *data; + + /** + * \brief Chunk length (in bytes). + */ + size_t len; +} br_tls_prf_seed_chunk; + +/** + * \brief PRF implementation for TLS 1.0 and 1.1. + * + * This PRF is the one specified by TLS 1.0 and 1.1. It internally uses + * MD5 and SHA-1. + * + * \param dst destination buffer. + * \param len output length (in bytes). + * \param secret secret value (key) for this computation. + * \param secret_len length of "secret" (in bytes). + * \param label PRF label (zero-terminated ASCII string). + * \param seed_num number of seed chunks. + * \param seed seed chnks for this computation (usually non-secret). + */ +void br_tls10_prf(void *dst, size_t len, + const void *secret, size_t secret_len, const char *label, + size_t seed_num, const br_tls_prf_seed_chunk *seed); + +/** + * \brief PRF implementation for TLS 1.2, with SHA-256. + * + * This PRF is the one specified by TLS 1.2, when the underlying hash + * function is SHA-256. + * + * \param dst destination buffer. + * \param len output length (in bytes). + * \param secret secret value (key) for this computation. + * \param secret_len length of "secret" (in bytes). + * \param label PRF label (zero-terminated ASCII string). + * \param seed_num number of seed chunks. + * \param seed seed chnks for this computation (usually non-secret). + */ +void br_tls12_sha256_prf(void *dst, size_t len, + const void *secret, size_t secret_len, const char *label, + size_t seed_num, const br_tls_prf_seed_chunk *seed); + +/** + * \brief PRF implementation for TLS 1.2, with SHA-384. + * + * This PRF is the one specified by TLS 1.2, when the underlying hash + * function is SHA-384. + * + * \param dst destination buffer. + * \param len output length (in bytes). + * \param secret secret value (key) for this computation. + * \param secret_len length of "secret" (in bytes). + * \param label PRF label (zero-terminated ASCII string). + * \param seed_num number of seed chunks. + * \param seed seed chnks for this computation (usually non-secret). + */ +void br_tls12_sha384_prf(void *dst, size_t len, + const void *secret, size_t secret_len, const char *label, + size_t seed_num, const br_tls_prf_seed_chunk *seed); + +/** + * brief A convenient type name for a PRF implementation. + * + * \param dst destination buffer. + * \param len output length (in bytes). + * \param secret secret value (key) for this computation. + * \param secret_len length of "secret" (in bytes). + * \param label PRF label (zero-terminated ASCII string). + * \param seed_num number of seed chunks. + * \param seed seed chnks for this computation (usually non-secret). + */ +typedef void (*br_tls_prf_impl)(void *dst, size_t len, + const void *secret, size_t secret_len, const char *label, + size_t seed_num, const br_tls_prf_seed_chunk *seed); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/enclave/include/bearssl/bearssl_rand.h b/enclave/include/bearssl/bearssl_rand.h new file mode 100644 index 0000000..0a9f544 --- /dev/null +++ b/enclave/include/bearssl/bearssl_rand.h @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_RAND_H__ +#define BR_BEARSSL_RAND_H__ + +#include +#include + +#include "bearssl_block.h" +#include "bearssl_hash.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file bearssl_rand.h + * + * # Pseudo-Random Generators + * + * A PRNG is a state-based engine that outputs pseudo-random bytes on + * demand. It is initialized with an initial seed, and additional seed + * bytes can be added afterwards. Bytes produced depend on the seeds and + * also on the exact sequence of calls (including sizes requested for + * each call). + * + * + * ## Procedural and OOP API + * + * For the PRNG of name "`xxx`", two API are provided. The _procedural_ + * API defined a context structure `br_xxx_context` and three functions: + * + * - `br_xxx_init()` + * + * Initialise the context with an initial seed. + * + * - `br_xxx_generate()` + * + * Produce some pseudo-random bytes. + * + * - `br_xxx_update()` + * + * Inject some additional seed. + * + * The initialisation function sets the first context field (`vtable`) + * to a pointer to the vtable that supports the OOP API. The OOP API + * provides access to the same functions through function pointers, + * named `init()`, `generate()` and `update()`. + * + * Note that the context initialisation method may accept additional + * parameters, provided as a 'const void *' pointer at API level. These + * additional parameters depend on the implemented PRNG. + * + * + * ## HMAC_DRBG + * + * HMAC_DRBG is defined in [NIST SP 800-90A Revision + * 1](http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf). + * It uses HMAC repeatedly, over some configurable underlying hash + * function. In BearSSL, it is implemented under the "`hmac_drbg`" name. + * The "extra parameters" pointer for context initialisation should be + * set to a pointer to the vtable for the underlying hash function (e.g. + * pointer to `br_sha256_vtable` to use HMAC_DRBG with SHA-256). + * + * According to the NIST standard, each request shall produce up to + * 219 bits (i.e. 64 kB of data); moreover, the context shall + * be reseeded at least once every 248 requests. This + * implementation does not maintain the reseed counter (the threshold is + * too high to be reached in practice) and does not object to producing + * more than 64 kB in a single request; thus, the code cannot fail, + * which corresponds to the fact that the API has no room for error + * codes. However, this implies that requesting more than 64 kB in one + * `generate()` request, or making more than 248 requests + * without reseeding, is formally out of NIST specification. There is + * no currently known security penalty for exceeding the NIST limits, + * and, in any case, HMAC_DRBG usage in implementing SSL/TLS always + * stays much below these thresholds. + * + * + * ## AESCTR_DRBG + * + * AESCTR_DRBG is a custom PRNG based on AES-128 in CTR mode. This is + * meant to be used only in situations where you are desperate for + * speed, and have an hardware-optimized AES/CTR implementation. Whether + * this will yield perceptible improvements depends on what you use the + * pseudorandom bytes for, and how many you want; for instance, RSA key + * pair generation uses a substantial amount of randomness, and using + * AESCTR_DRBG instead of HMAC_DRBG yields a 15 to 20% increase in key + * generation speed on a recent x86 CPU (Intel Core i7-6567U at 3.30 GHz). + * + * Internally, it uses CTR mode with successive counter values, starting + * at zero (counter value expressed over 128 bits, big-endian convention). + * The counter is not allowed to reach 32768; thus, every 32768*16 bytes + * at most, the `update()` function is run (on an empty seed, if none is + * provided). The `update()` function computes the new AES-128 key by + * applying a custom hash function to the concatenation of a state-dependent + * word (encryption of an all-one block with the current key) and the new + * seed. The custom hash function uses Hirose's construction over AES-256; + * see the comments in `aesctr_drbg.c` for details. + * + * This DRBG does not follow an existing standard, and thus should be + * considered as inadequate for production use until it has been properly + * analysed. + */ + +/** + * \brief Class type for PRNG implementations. + * + * A `br_prng_class` instance references the methods implementing a PRNG. + * Constant instances of this structure are defined for each implemented + * PRNG. Such instances are also called "vtables". + */ +typedef struct br_prng_class_ br_prng_class; +struct br_prng_class_ { + /** + * \brief Size (in bytes) of the context structure appropriate for + * running this PRNG. + */ + size_t context_size; + + /** + * \brief Initialisation method. + * + * The context to initialise is provided as a pointer to its + * first field (the vtable pointer); this function sets that + * first field to a pointer to the vtable. + * + * The extra parameters depend on the implementation; each + * implementation defines what kind of extra parameters it + * expects (if any). + * + * Requirements on the initial seed depend on the implemented + * PRNG. + * + * \param ctx PRNG context to initialise. + * \param params extra parameters for the PRNG. + * \param seed initial seed. + * \param seed_len initial seed length (in bytes). + */ + void (*init)(const br_prng_class **ctx, const void *params, + const void *seed, size_t seed_len); + + /** + * \brief Random bytes generation. + * + * This method produces `len` pseudorandom bytes, in the `out` + * buffer. The context is updated accordingly. + * + * \param ctx PRNG context. + * \param out output buffer. + * \param len number of pseudorandom bytes to produce. + */ + void (*generate)(const br_prng_class **ctx, void *out, size_t len); + + /** + * \brief Inject additional seed bytes. + * + * The provided seed bytes are added into the PRNG internal + * entropy pool. + * + * \param ctx PRNG context. + * \param seed additional seed. + * \param seed_len additional seed length (in bytes). + */ + void (*update)(const br_prng_class **ctx, + const void *seed, size_t seed_len); +}; + +/** + * \brief Context for HMAC_DRBG. + * + * The context contents are opaque, except the first field, which + * supports OOP. + */ +typedef struct { + /** + * \brief Pointer to the vtable. + * + * This field is set with the initialisation method/function. + */ + const br_prng_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + unsigned char K[64]; + unsigned char V[64]; + const br_hash_class *digest_class; +#endif +} br_hmac_drbg_context; + +/** + * \brief Statically allocated, constant vtable for HMAC_DRBG. + */ +extern const br_prng_class br_hmac_drbg_vtable; + +/** + * \brief HMAC_DRBG initialisation. + * + * The context to initialise is provided as a pointer to its first field + * (the vtable pointer); this function sets that first field to a + * pointer to the vtable. + * + * The `seed` value is what is called, in NIST terminology, the + * concatenation of the "seed", "nonce" and "personalization string", in + * that order. + * + * The `digest_class` parameter defines the underlying hash function. + * Formally, the NIST standard specifies that the hash function shall + * be only SHA-1 or one of the SHA-2 functions. This implementation also + * works with any other implemented hash function (such as MD5), but + * this is non-standard and therefore not recommended. + * + * \param ctx HMAC_DRBG context to initialise. + * \param digest_class vtable for the underlying hash function. + * \param seed initial seed. + * \param seed_len initial seed length (in bytes). + */ +void br_hmac_drbg_init(br_hmac_drbg_context *ctx, + const br_hash_class *digest_class, const void *seed, size_t seed_len); + +/** + * \brief Random bytes generation with HMAC_DRBG. + * + * This method produces `len` pseudorandom bytes, in the `out` + * buffer. The context is updated accordingly. Formally, requesting + * more than 65536 bytes in one request falls out of specification + * limits (but it won't fail). + * + * \param ctx HMAC_DRBG context. + * \param out output buffer. + * \param len number of pseudorandom bytes to produce. + */ +void br_hmac_drbg_generate(br_hmac_drbg_context *ctx, void *out, size_t len); + +/** + * \brief Inject additional seed bytes in HMAC_DRBG. + * + * The provided seed bytes are added into the HMAC_DRBG internal + * entropy pool. The process does not _replace_ existing entropy, + * thus pushing non-random bytes (i.e. bytes which are known to the + * attackers) does not degrade the overall quality of generated bytes. + * + * \param ctx HMAC_DRBG context. + * \param seed additional seed. + * \param seed_len additional seed length (in bytes). + */ +void br_hmac_drbg_update(br_hmac_drbg_context *ctx, + const void *seed, size_t seed_len); + +/** + * \brief Get the hash function implementation used by a given instance of + * HMAC_DRBG. + * + * This calls MUST NOT be performed on a context which was not + * previously initialised. + * + * \param ctx HMAC_DRBG context. + * \return the hash function vtable. + */ +static inline const br_hash_class * +br_hmac_drbg_get_hash(const br_hmac_drbg_context *ctx) +{ + return ctx->digest_class; +} + +/** + * \brief Type for a provider of entropy seeds. + * + * A "seeder" is a function that is able to obtain random values from + * some source and inject them as entropy seed in a PRNG. A seeder + * shall guarantee that the total entropy of the injected seed is large + * enough to seed a PRNG for purposes of cryptographic key generation + * (i.e. at least 128 bits). + * + * A seeder may report a failure to obtain adequate entropy. Seeders + * shall endeavour to fix themselves transient errors by trying again; + * thus, callers may consider reported errors as permanent. + * + * \param ctx PRNG context to seed. + * \return 1 on success, 0 on error. + */ +typedef int (*br_prng_seeder)(const br_prng_class **ctx); + +/** + * \brief Get a seeder backed by the operating system or hardware. + * + * Get a seeder that feeds on RNG facilities provided by the current + * operating system or hardware. If no such facility is known, then 0 + * is returned. + * + * If `name` is not `NULL`, then `*name` is set to a symbolic string + * that identifies the seeder implementation. If no seeder is returned + * and `name` is not `NULL`, then `*name` is set to a pointer to the + * constant string `"none"`. + * + * \param name receiver for seeder name, or `NULL`. + * \return the system seeder, if available, or 0. + */ +br_prng_seeder br_prng_seeder_system(const char **name); + +/** + * \brief Context for AESCTR_DRBG. + * + * The context contents are opaque, except the first field, which + * supports OOP. + */ +typedef struct { + /** + * \brief Pointer to the vtable. + * + * This field is set with the initialisation method/function. + */ + const br_prng_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + br_aes_gen_ctr_keys sk; + uint32_t cc; +#endif +} br_aesctr_drbg_context; + +/** + * \brief Statically allocated, constant vtable for AESCTR_DRBG. + */ +extern const br_prng_class br_aesctr_drbg_vtable; + +/** + * \brief AESCTR_DRBG initialisation. + * + * The context to initialise is provided as a pointer to its first field + * (the vtable pointer); this function sets that first field to a + * pointer to the vtable. + * + * The internal AES key is first set to the all-zero key; then, the + * `br_aesctr_drbg_update()` function is called with the provided `seed`. + * The call is performed even if the seed length (`seed_len`) is zero. + * + * The `aesctr` parameter defines the underlying AES/CTR implementation. + * + * \param ctx AESCTR_DRBG context to initialise. + * \param aesctr vtable for the AES/CTR implementation. + * \param seed initial seed (can be `NULL` if `seed_len` is zero). + * \param seed_len initial seed length (in bytes). + */ +void br_aesctr_drbg_init(br_aesctr_drbg_context *ctx, + const br_block_ctr_class *aesctr, const void *seed, size_t seed_len); + +/** + * \brief Random bytes generation with AESCTR_DRBG. + * + * This method produces `len` pseudorandom bytes, in the `out` + * buffer. The context is updated accordingly. + * + * \param ctx AESCTR_DRBG context. + * \param out output buffer. + * \param len number of pseudorandom bytes to produce. + */ +void br_aesctr_drbg_generate(br_aesctr_drbg_context *ctx, + void *out, size_t len); + +/** + * \brief Inject additional seed bytes in AESCTR_DRBG. + * + * The provided seed bytes are added into the AESCTR_DRBG internal + * entropy pool. The process does not _replace_ existing entropy, + * thus pushing non-random bytes (i.e. bytes which are known to the + * attackers) does not degrade the overall quality of generated bytes. + * + * \param ctx AESCTR_DRBG context. + * \param seed additional seed. + * \param seed_len additional seed length (in bytes). + */ +void br_aesctr_drbg_update(br_aesctr_drbg_context *ctx, + const void *seed, size_t seed_len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/enclave/include/bearssl/bearssl_rsa.h b/enclave/include/bearssl/bearssl_rsa.h new file mode 100644 index 0000000..0a069fd --- /dev/null +++ b/enclave/include/bearssl/bearssl_rsa.h @@ -0,0 +1,1655 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_RSA_H__ +#define BR_BEARSSL_RSA_H__ + +#include +#include + +#include "bearssl_hash.h" +#include "bearssl_rand.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file bearssl_rsa.h + * + * # RSA + * + * This file documents the RSA implementations provided with BearSSL. + * Note that the SSL engine accesses these implementations through a + * configurable API, so it is possible to, for instance, run a SSL + * server which uses a RSA engine which is not based on this code. + * + * ## Key Elements + * + * RSA public and private keys consist in lists of big integers. All + * such integers are represented with big-endian unsigned notation: + * first byte is the most significant, and the value is positive (so + * there is no dedicated "sign bit"). Public and private key structures + * thus contain, for each such integer, a pointer to the first value byte + * (`unsigned char *`), and a length (`size_t`) which is the number of + * relevant bytes. As a general rule, minimal-length encoding is not + * enforced: values may have extra leading bytes of value 0. + * + * RSA public keys consist in two integers: + * + * - the modulus (`n`); + * - the public exponent (`e`). + * + * RSA private keys, as defined in + * [PKCS#1](https://tools.ietf.org/html/rfc3447), contain eight integers: + * + * - the modulus (`n`); + * - the public exponent (`e`); + * - the private exponent (`d`); + * - the first prime factor (`p`); + * - the second prime factor (`q`); + * - the first reduced exponent (`dp`, which is `d` modulo `p-1`); + * - the second reduced exponent (`dq`, which is `d` modulo `q-1`); + * - the CRT coefficient (`iq`, the inverse of `q` modulo `p`). + * + * However, the implementations defined in BearSSL use only five of + * these integers: `p`, `q`, `dp`, `dq` and `iq`. + * + * ## Security Features and Limitations + * + * The implementations contained in BearSSL have the following limitations + * and features: + * + * - They are constant-time. This means that the execution time and + * memory access pattern may depend on the _lengths_ of the private + * key components, but not on their value, nor on the value of + * the operand. Note that this property is not achieved through + * random masking, but "true" constant-time code. + * + * - They support only private keys with two prime factors. RSA private + * keys with three or more prime factors are nominally supported, but + * rarely used; they may offer faster operations, at the expense of + * more code and potentially a reduction in security if there are + * "too many" prime factors. + * + * - The public exponent may have arbitrary length. Of course, it is + * a good idea to keep public exponents small, so that public key + * operations are fast; but, contrary to some widely deployed + * implementations, BearSSL has no problem with public exponents + * longer than 32 bits. + * + * - The two prime factors of the modulus need not have the same length + * (but severely imbalanced factor lengths might reduce security). + * Similarly, there is no requirement that the first factor (`p`) + * be greater than the second factor (`q`). + * + * - Prime factors and modulus must be smaller than a compile-time limit. + * This is made necessary by the use of fixed-size stack buffers, and + * the limit has been adjusted to keep stack usage under 2 kB for the + * RSA operations. Currently, the maximum modulus size is 4096 bits, + * and the maximum prime factor size is 2080 bits. + * + * - The RSA functions themselves do not enforce lower size limits, + * except that which is absolutely necessary for the operation to + * mathematically make sense (e.g. a PKCS#1 v1.5 signature with + * SHA-1 requires a modulus of at least 361 bits). It is up to users + * of this code to enforce size limitations when appropriate (e.g. + * the X.509 validation engine, by default, rejects RSA keys of + * less than 1017 bits). + * + * - Within the size constraints expressed above, arbitrary bit lengths + * are supported. There is no requirement that prime factors or + * modulus have a size multiple of 8 or 16. + * + * - When verifying PKCS#1 v1.5 signatures, both variants of the hash + * function identifying header (with and without the ASN.1 NULL) are + * supported. When producing such signatures, the variant with the + * ASN.1 NULL is used. + * + * ## Implementations + * + * Three RSA implementations are included: + * + * - The **i32** implementation internally represents big integers + * as arrays of 32-bit integers. It is perfunctory and portable, + * but not very efficient. + * + * - The **i31** implementation uses 32-bit integers, each containing + * 31 bits worth of integer data. The i31 implementation is somewhat + * faster than the i32 implementation (the reduced integer size makes + * carry propagation easier) for a similar code footprint, but uses + * very slightly larger stack buffers (about 4% bigger). + * + * - The **i62** implementation is similar to the i31 implementation, + * except that it internally leverages the 64x64->128 multiplication + * opcode. This implementation is available only on architectures + * where such an opcode exists. It is much faster than i31. + * + * - The **i15** implementation uses 16-bit integers, each containing + * 15 bits worth of integer data. Multiplication results fit on + * 32 bits, so this won't use the "widening" multiplication routine + * on ARM Cortex M0/M0+, for much better performance and constant-time + * execution. + */ + +/** + * \brief RSA public key. + * + * The structure references the modulus and the public exponent. Both + * integers use unsigned big-endian representation; extra leading bytes + * of value 0 are allowed. + */ +typedef struct { + /** \brief Modulus. */ + unsigned char *n; + /** \brief Modulus length (in bytes). */ + size_t nlen; + /** \brief Public exponent. */ + unsigned char *e; + /** \brief Public exponent length (in bytes). */ + size_t elen; +} br_rsa_public_key; + +/** + * \brief RSA private key. + * + * The structure references the private factors, reduced private + * exponents, and CRT coefficient. It also contains the bit length of + * the modulus. The big integers use unsigned big-endian representation; + * extra leading bytes of value 0 are allowed. However, the modulus bit + * length (`n_bitlen`) MUST be exact. + */ +typedef struct { + /** \brief Modulus bit length (in bits, exact value). */ + uint32_t n_bitlen; + /** \brief First prime factor. */ + unsigned char *p; + /** \brief First prime factor length (in bytes). */ + size_t plen; + /** \brief Second prime factor. */ + unsigned char *q; + /** \brief Second prime factor length (in bytes). */ + size_t qlen; + /** \brief First reduced private exponent. */ + unsigned char *dp; + /** \brief First reduced private exponent length (in bytes). */ + size_t dplen; + /** \brief Second reduced private exponent. */ + unsigned char *dq; + /** \brief Second reduced private exponent length (in bytes). */ + size_t dqlen; + /** \brief CRT coefficient. */ + unsigned char *iq; + /** \brief CRT coefficient length (in bytes). */ + size_t iqlen; +} br_rsa_private_key; + +/** + * \brief Type for a RSA public key engine. + * + * The public key engine performs the modular exponentiation of the + * provided value with the public exponent. The value is modified in + * place. + * + * The value length (`xlen`) is verified to have _exactly_ the same + * length as the modulus (actual modulus length, without extra leading + * zeros in the modulus representation in memory). If the length does + * not match, then this function returns 0 and `x[]` is unmodified. + * + * It `xlen` is correct, then `x[]` is modified. Returned value is 1 + * on success, 0 on error. Error conditions include an oversized `x[]` + * (the array has the same length as the modulus, but the numerical value + * is not lower than the modulus) and an invalid modulus (e.g. an even + * integer). If an error is reported, then the new contents of `x[]` are + * unspecified. + * + * \param x operand to exponentiate. + * \param xlen length of the operand (in bytes). + * \param pk RSA public key. + * \return 1 on success, 0 on error. + */ +typedef uint32_t (*br_rsa_public)(unsigned char *x, size_t xlen, + const br_rsa_public_key *pk); + +/** + * \brief Type for a RSA signature verification engine (PKCS#1 v1.5). + * + * Parameters are: + * + * - The signature itself. The provided array is NOT modified. + * + * - The encoded OID for the hash function. The provided array must begin + * with a single byte that contains the length of the OID value (in + * bytes), followed by exactly that many bytes. This parameter may + * also be `NULL`, in which case the raw hash value should be used + * with the PKCS#1 v1.5 "type 1" padding (as used in SSL/TLS up + * to TLS-1.1, with a 36-byte hash value). + * + * - The hash output length, in bytes. + * + * - The public key. + * + * - An output buffer for the hash value. The caller must still compare + * it with the hash of the data over which the signature is computed. + * + * **Constraints:** + * + * - Hash length MUST be no more than 64 bytes. + * + * - OID value length MUST be no more than 32 bytes (i.e. `hash_oid[0]` + * must have a value in the 0..32 range, inclusive). + * + * This function verifies that the signature length (`xlen`) matches the + * modulus length (this function returns 0 on mismatch). If the modulus + * size exceeds the maximum supported RSA size, then the function also + * returns 0. + * + * Returned value is 1 on success, 0 on error. + * + * Implementations of this type need not be constant-time. + * + * \param x signature buffer. + * \param xlen signature length (in bytes). + * \param hash_oid encoded hash algorithm OID (or `NULL`). + * \param hash_len expected hash value length (in bytes). + * \param pk RSA public key. + * \param hash_out output buffer for the hash value. + * \return 1 on success, 0 on error. + */ +typedef uint32_t (*br_rsa_pkcs1_vrfy)(const unsigned char *x, size_t xlen, + const unsigned char *hash_oid, size_t hash_len, + const br_rsa_public_key *pk, unsigned char *hash_out); + +/** + * \brief Type for a RSA signature verification engine (PSS). + * + * Parameters are: + * + * - The signature itself. The provided array is NOT modified. + * + * - The hash function which was used to hash the message. + * + * - The hash function to use with MGF1 within the PSS padding. This + * is not necessarily the same hash function as the one which was + * used to hash the signed message. + * + * - The hashed message (as an array of bytes). + * + * - The PSS salt length (in bytes). + * + * - The public key. + * + * **Constraints:** + * + * - Hash message length MUST be no more than 64 bytes. + * + * Note that, contrary to PKCS#1 v1.5 signature, the hash value of the + * signed data cannot be extracted from the signature; it must be + * provided to the verification function. + * + * This function verifies that the signature length (`xlen`) matches the + * modulus length (this function returns 0 on mismatch). If the modulus + * size exceeds the maximum supported RSA size, then the function also + * returns 0. + * + * Returned value is 1 on success, 0 on error. + * + * Implementations of this type need not be constant-time. + * + * \param x signature buffer. + * \param xlen signature length (in bytes). + * \param hf_data hash function applied on the message. + * \param hf_mgf1 hash function to use with MGF1. + * \param hash hash value of the signed message. + * \param salt_len PSS salt length (in bytes). + * \param pk RSA public key. + * \return 1 on success, 0 on error. + */ +typedef uint32_t (*br_rsa_pss_vrfy)(const unsigned char *x, size_t xlen, + const br_hash_class *hf_data, const br_hash_class *hf_mgf1, + const void *hash, size_t salt_len, const br_rsa_public_key *pk); + +/** + * \brief Type for a RSA encryption engine (OAEP). + * + * Parameters are: + * + * - A source of random bytes. The source must be already initialized. + * + * - A hash function, used internally with the mask generation function + * (MGF1). + * + * - A label. The `label` pointer may be `NULL` if `label_len` is zero + * (an empty label, which is the default in PKCS#1 v2.2). + * + * - The public key. + * + * - The destination buffer. Its maximum length (in bytes) is provided; + * if that length is lower than the public key length, then an error + * is reported. + * + * - The source message. + * + * The encrypted message output has exactly the same length as the modulus + * (mathematical length, in bytes, not counting extra leading zeros in the + * modulus representation in the public key). + * + * The source message (`src`, length `src_len`) may overlap with the + * destination buffer (`dst`, length `dst_max_len`). + * + * This function returns the actual encrypted message length, in bytes; + * on error, zero is returned. An error is reported if the output buffer + * is not large enough, or the public is invalid, or the public key + * modulus exceeds the maximum supported RSA size. + * + * \param rnd source of random bytes. + * \param dig hash function to use with MGF1. + * \param label label value (may be `NULL` if `label_len` is zero). + * \param label_len label length, in bytes. + * \param pk RSA public key. + * \param dst destination buffer. + * \param dst_max_len destination buffer length (maximum encrypted data size). + * \param src message to encrypt. + * \param src_len source message length (in bytes). + * \return encrypted message length (in bytes), or 0 on error. + */ +typedef size_t (*br_rsa_oaep_encrypt)( + const br_prng_class **rnd, const br_hash_class *dig, + const void *label, size_t label_len, + const br_rsa_public_key *pk, + void *dst, size_t dst_max_len, + const void *src, size_t src_len); + +/** + * \brief Type for a RSA private key engine. + * + * The `x[]` buffer is modified in place, and its length is inferred from + * the modulus length (`x[]` is assumed to have a length of + * `(sk->n_bitlen+7)/8` bytes). + * + * Returned value is 1 on success, 0 on error. + * + * \param x operand to exponentiate. + * \param sk RSA private key. + * \return 1 on success, 0 on error. + */ +typedef uint32_t (*br_rsa_private)(unsigned char *x, + const br_rsa_private_key *sk); + +/** + * \brief Type for a RSA signature generation engine (PKCS#1 v1.5). + * + * Parameters are: + * + * - The encoded OID for the hash function. The provided array must begin + * with a single byte that contains the length of the OID value (in + * bytes), followed by exactly that many bytes. This parameter may + * also be `NULL`, in which case the raw hash value should be used + * with the PKCS#1 v1.5 "type 1" padding (as used in SSL/TLS up + * to TLS-1.1, with a 36-byte hash value). + * + * - The hash value computes over the data to sign (its length is + * expressed in bytes). + * + * - The RSA private key. + * + * - The output buffer, that receives the signature. + * + * Returned value is 1 on success, 0 on error. Error conditions include + * a too small modulus for the provided hash OID and value, or some + * invalid key parameters. The signature length is exactly + * `(sk->n_bitlen+7)/8` bytes. + * + * This function is expected to be constant-time with regards to the + * private key bytes (lengths of the modulus and the individual factors + * may leak, though) and to the hashed data. + * + * \param hash_oid encoded hash algorithm OID (or `NULL`). + * \param hash hash value. + * \param hash_len hash value length (in bytes). + * \param sk RSA private key. + * \param x output buffer for the signature value. + * \return 1 on success, 0 on error. + */ +typedef uint32_t (*br_rsa_pkcs1_sign)(const unsigned char *hash_oid, + const unsigned char *hash, size_t hash_len, + const br_rsa_private_key *sk, unsigned char *x); + +/** + * \brief Type for a RSA signature generation engine (PSS). + * + * Parameters are: + * + * - An initialized PRNG for salt generation. If the salt length is + * zero (`salt_len` parameter), then the PRNG is optional (this is + * not the typical case, as the security proof of RSA/PSS is + * tighter when a non-empty salt is used). + * + * - The hash function which was used to hash the message. + * + * - The hash function to use with MGF1 within the PSS padding. This + * is not necessarily the same function as the one used to hash the + * message. + * + * - The hashed message. + * + * - The salt length, in bytes. + * + * - The RSA private key. + * + * - The output buffer, that receives the signature. + * + * Returned value is 1 on success, 0 on error. Error conditions include + * a too small modulus for the provided hash and salt lengths, or some + * invalid key parameters. The signature length is exactly + * `(sk->n_bitlen+7)/8` bytes. + * + * This function is expected to be constant-time with regards to the + * private key bytes (lengths of the modulus and the individual factors + * may leak, though) and to the hashed data. + * + * \param rng PRNG for salt generation (`NULL` if `salt_len` is zero). + * \param hf_data hash function used to hash the signed data. + * \param hf_mgf1 hash function to use with MGF1. + * \param hash hashed message. + * \param salt_len salt length (in bytes). + * \param sk RSA private key. + * \param x output buffer for the signature value. + * \return 1 on success, 0 on error. + */ +typedef uint32_t (*br_rsa_pss_sign)(const br_prng_class **rng, + const br_hash_class *hf_data, const br_hash_class *hf_mgf1, + const unsigned char *hash_value, size_t salt_len, + const br_rsa_private_key *sk, unsigned char *x); + +/** + * \brief Encoded OID for SHA-1 (in RSA PKCS#1 signatures). + */ +#define BR_HASH_OID_SHA1 \ + ((const unsigned char *)"\x05\x2B\x0E\x03\x02\x1A") + +/** + * \brief Encoded OID for SHA-224 (in RSA PKCS#1 signatures). + */ +#define BR_HASH_OID_SHA224 \ + ((const unsigned char *)"\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04") + +/** + * \brief Encoded OID for SHA-256 (in RSA PKCS#1 signatures). + */ +#define BR_HASH_OID_SHA256 \ + ((const unsigned char *)"\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01") + +/** + * \brief Encoded OID for SHA-384 (in RSA PKCS#1 signatures). + */ +#define BR_HASH_OID_SHA384 \ + ((const unsigned char *)"\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02") + +/** + * \brief Encoded OID for SHA-512 (in RSA PKCS#1 signatures). + */ +#define BR_HASH_OID_SHA512 \ + ((const unsigned char *)"\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03") + +/** + * \brief Type for a RSA decryption engine (OAEP). + * + * Parameters are: + * + * - A hash function, used internally with the mask generation function + * (MGF1). + * + * - A label. The `label` pointer may be `NULL` if `label_len` is zero + * (an empty label, which is the default in PKCS#1 v2.2). + * + * - The private key. + * + * - The source and destination buffer. The buffer initially contains + * the encrypted message; the buffer contents are altered, and the + * decrypted message is written at the start of that buffer + * (decrypted message is always shorter than the encrypted message). + * + * If decryption fails in any way, then `*len` is unmodified, and the + * function returns 0. Otherwise, `*len` is set to the decrypted message + * length, and 1 is returned. The implementation is responsible for + * checking that the input message length matches the key modulus length, + * and that the padding is correct. + * + * Implementations MUST use constant-time check of the validity of the + * OAEP padding, at least until the leading byte and hash value have + * been checked. Whether overall decryption worked, and the length of + * the decrypted message, may leak. + * + * \param dig hash function to use with MGF1. + * \param label label value (may be `NULL` if `label_len` is zero). + * \param label_len label length, in bytes. + * \param sk RSA private key. + * \param data input/output buffer. + * \param len encrypted/decrypted message length. + * \return 1 on success, 0 on error. + */ +typedef uint32_t (*br_rsa_oaep_decrypt)( + const br_hash_class *dig, const void *label, size_t label_len, + const br_rsa_private_key *sk, void *data, size_t *len); + +/* + * RSA "i32" engine. Integers are internally represented as arrays of + * 32-bit integers, and the core multiplication primitive is the + * 32x32->64 multiplication. + */ + +/** + * \brief RSA public key engine "i32". + * + * \see br_rsa_public + * + * \param x operand to exponentiate. + * \param xlen length of the operand (in bytes). + * \param pk RSA public key. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i32_public(unsigned char *x, size_t xlen, + const br_rsa_public_key *pk); + +/** + * \brief RSA signature verification engine "i32" (PKCS#1 v1.5 signatures). + * + * \see br_rsa_pkcs1_vrfy + * + * \param x signature buffer. + * \param xlen signature length (in bytes). + * \param hash_oid encoded hash algorithm OID (or `NULL`). + * \param hash_len expected hash value length (in bytes). + * \param pk RSA public key. + * \param hash_out output buffer for the hash value. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i32_pkcs1_vrfy(const unsigned char *x, size_t xlen, + const unsigned char *hash_oid, size_t hash_len, + const br_rsa_public_key *pk, unsigned char *hash_out); + +/** + * \brief RSA signature verification engine "i32" (PSS signatures). + * + * \see br_rsa_pss_vrfy + * + * \param x signature buffer. + * \param xlen signature length (in bytes). + * \param hf_data hash function applied on the message. + * \param hf_mgf1 hash function to use with MGF1. + * \param hash hash value of the signed message. + * \param salt_len PSS salt length (in bytes). + * \param pk RSA public key. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i32_pss_vrfy(const unsigned char *x, size_t xlen, + const br_hash_class *hf_data, const br_hash_class *hf_mgf1, + const void *hash, size_t salt_len, const br_rsa_public_key *pk); + +/** + * \brief RSA private key engine "i32". + * + * \see br_rsa_private + * + * \param x operand to exponentiate. + * \param sk RSA private key. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i32_private(unsigned char *x, + const br_rsa_private_key *sk); + +/** + * \brief RSA signature generation engine "i32" (PKCS#1 v1.5 signatures). + * + * \see br_rsa_pkcs1_sign + * + * \param hash_oid encoded hash algorithm OID (or `NULL`). + * \param hash hash value. + * \param hash_len hash value length (in bytes). + * \param sk RSA private key. + * \param x output buffer for the hash value. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i32_pkcs1_sign(const unsigned char *hash_oid, + const unsigned char *hash, size_t hash_len, + const br_rsa_private_key *sk, unsigned char *x); + +/** + * \brief RSA signature generation engine "i32" (PSS signatures). + * + * \see br_rsa_pss_sign + * + * \param rng PRNG for salt generation (`NULL` if `salt_len` is zero). + * \param hf_data hash function used to hash the signed data. + * \param hf_mgf1 hash function to use with MGF1. + * \param hash hashed message. + * \param salt_len salt length (in bytes). + * \param sk RSA private key. + * \param x output buffer for the signature value. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i32_pss_sign(const br_prng_class **rng, + const br_hash_class *hf_data, const br_hash_class *hf_mgf1, + const unsigned char *hash_value, size_t salt_len, + const br_rsa_private_key *sk, unsigned char *x); + +/* + * RSA "i31" engine. Similar to i32, but only 31 bits are used per 32-bit + * word. This uses slightly more stack space (about 4% more) and code + * space, but it quite faster. + */ + +/** + * \brief RSA public key engine "i31". + * + * \see br_rsa_public + * + * \param x operand to exponentiate. + * \param xlen length of the operand (in bytes). + * \param pk RSA public key. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i31_public(unsigned char *x, size_t xlen, + const br_rsa_public_key *pk); + +/** + * \brief RSA signature verification engine "i31" (PKCS#1 v1.5 signatures). + * + * \see br_rsa_pkcs1_vrfy + * + * \param x signature buffer. + * \param xlen signature length (in bytes). + * \param hash_oid encoded hash algorithm OID (or `NULL`). + * \param hash_len expected hash value length (in bytes). + * \param pk RSA public key. + * \param hash_out output buffer for the hash value. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i31_pkcs1_vrfy(const unsigned char *x, size_t xlen, + const unsigned char *hash_oid, size_t hash_len, + const br_rsa_public_key *pk, unsigned char *hash_out); + +/** + * \brief RSA signature verification engine "i31" (PSS signatures). + * + * \see br_rsa_pss_vrfy + * + * \param x signature buffer. + * \param xlen signature length (in bytes). + * \param hf_data hash function applied on the message. + * \param hf_mgf1 hash function to use with MGF1. + * \param hash hash value of the signed message. + * \param salt_len PSS salt length (in bytes). + * \param pk RSA public key. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i31_pss_vrfy(const unsigned char *x, size_t xlen, + const br_hash_class *hf_data, const br_hash_class *hf_mgf1, + const void *hash, size_t salt_len, const br_rsa_public_key *pk); + +/** + * \brief RSA private key engine "i31". + * + * \see br_rsa_private + * + * \param x operand to exponentiate. + * \param sk RSA private key. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i31_private(unsigned char *x, + const br_rsa_private_key *sk); + +/** + * \brief RSA signature generation engine "i31" (PKCS#1 v1.5 signatures). + * + * \see br_rsa_pkcs1_sign + * + * \param hash_oid encoded hash algorithm OID (or `NULL`). + * \param hash hash value. + * \param hash_len hash value length (in bytes). + * \param sk RSA private key. + * \param x output buffer for the hash value. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i31_pkcs1_sign(const unsigned char *hash_oid, + const unsigned char *hash, size_t hash_len, + const br_rsa_private_key *sk, unsigned char *x); + +/** + * \brief RSA signature generation engine "i31" (PSS signatures). + * + * \see br_rsa_pss_sign + * + * \param rng PRNG for salt generation (`NULL` if `salt_len` is zero). + * \param hf_data hash function used to hash the signed data. + * \param hf_mgf1 hash function to use with MGF1. + * \param hash hashed message. + * \param salt_len salt length (in bytes). + * \param sk RSA private key. + * \param x output buffer for the signature value. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i31_pss_sign(const br_prng_class **rng, + const br_hash_class *hf_data, const br_hash_class *hf_mgf1, + const unsigned char *hash_value, size_t salt_len, + const br_rsa_private_key *sk, unsigned char *x); + +/* + * RSA "i62" engine. Similar to i31, but internal multiplication use + * 64x64->128 multiplications. This is available only on architecture + * that offer such an opcode. + */ + +/** + * \brief RSA public key engine "i62". + * + * This function is defined only on architecture that offer a 64x64->128 + * opcode. Use `br_rsa_i62_public_get()` to dynamically obtain a pointer + * to that function. + * + * \see br_rsa_public + * + * \param x operand to exponentiate. + * \param xlen length of the operand (in bytes). + * \param pk RSA public key. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i62_public(unsigned char *x, size_t xlen, + const br_rsa_public_key *pk); + +/** + * \brief RSA signature verification engine "i62" (PKCS#1 v1.5 signatures). + * + * This function is defined only on architecture that offer a 64x64->128 + * opcode. Use `br_rsa_i62_pkcs1_vrfy_get()` to dynamically obtain a pointer + * to that function. + * + * \see br_rsa_pkcs1_vrfy + * + * \param x signature buffer. + * \param xlen signature length (in bytes). + * \param hash_oid encoded hash algorithm OID (or `NULL`). + * \param hash_len expected hash value length (in bytes). + * \param pk RSA public key. + * \param hash_out output buffer for the hash value. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i62_pkcs1_vrfy(const unsigned char *x, size_t xlen, + const unsigned char *hash_oid, size_t hash_len, + const br_rsa_public_key *pk, unsigned char *hash_out); + +/** + * \brief RSA signature verification engine "i62" (PSS signatures). + * + * This function is defined only on architecture that offer a 64x64->128 + * opcode. Use `br_rsa_i62_pss_vrfy_get()` to dynamically obtain a pointer + * to that function. + * + * \see br_rsa_pss_vrfy + * + * \param x signature buffer. + * \param xlen signature length (in bytes). + * \param hf_data hash function applied on the message. + * \param hf_mgf1 hash function to use with MGF1. + * \param hash hash value of the signed message. + * \param salt_len PSS salt length (in bytes). + * \param pk RSA public key. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i62_pss_vrfy(const unsigned char *x, size_t xlen, + const br_hash_class *hf_data, const br_hash_class *hf_mgf1, + const void *hash, size_t salt_len, const br_rsa_public_key *pk); + +/** + * \brief RSA private key engine "i62". + * + * This function is defined only on architecture that offer a 64x64->128 + * opcode. Use `br_rsa_i62_private_get()` to dynamically obtain a pointer + * to that function. + * + * \see br_rsa_private + * + * \param x operand to exponentiate. + * \param sk RSA private key. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i62_private(unsigned char *x, + const br_rsa_private_key *sk); + +/** + * \brief RSA signature generation engine "i62" (PKCS#1 v1.5 signatures). + * + * This function is defined only on architecture that offer a 64x64->128 + * opcode. Use `br_rsa_i62_pkcs1_sign_get()` to dynamically obtain a pointer + * to that function. + * + * \see br_rsa_pkcs1_sign + * + * \param hash_oid encoded hash algorithm OID (or `NULL`). + * \param hash hash value. + * \param hash_len hash value length (in bytes). + * \param sk RSA private key. + * \param x output buffer for the hash value. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i62_pkcs1_sign(const unsigned char *hash_oid, + const unsigned char *hash, size_t hash_len, + const br_rsa_private_key *sk, unsigned char *x); + +/** + * \brief RSA signature generation engine "i62" (PSS signatures). + * + * This function is defined only on architecture that offer a 64x64->128 + * opcode. Use `br_rsa_i62_pss_sign_get()` to dynamically obtain a pointer + * to that function. + * + * \see br_rsa_pss_sign + * + * \param rng PRNG for salt generation (`NULL` if `salt_len` is zero). + * \param hf_data hash function used to hash the signed data. + * \param hf_mgf1 hash function to use with MGF1. + * \param hash hashed message. + * \param salt_len salt length (in bytes). + * \param sk RSA private key. + * \param x output buffer for the signature value. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i62_pss_sign(const br_prng_class **rng, + const br_hash_class *hf_data, const br_hash_class *hf_mgf1, + const unsigned char *hash_value, size_t salt_len, + const br_rsa_private_key *sk, unsigned char *x); + +/** + * \brief Get the RSA "i62" implementation (public key operations), + * if available. + * + * \return the implementation, or 0. + */ +br_rsa_public br_rsa_i62_public_get(void); + +/** + * \brief Get the RSA "i62" implementation (PKCS#1 v1.5 signature verification), + * if available. + * + * \return the implementation, or 0. + */ +br_rsa_pkcs1_vrfy br_rsa_i62_pkcs1_vrfy_get(void); + +/** + * \brief Get the RSA "i62" implementation (PSS signature verification), + * if available. + * + * \return the implementation, or 0. + */ +br_rsa_pss_vrfy br_rsa_i62_pss_vrfy_get(void); + +/** + * \brief Get the RSA "i62" implementation (private key operations), + * if available. + * + * \return the implementation, or 0. + */ +br_rsa_private br_rsa_i62_private_get(void); + +/** + * \brief Get the RSA "i62" implementation (PKCS#1 v1.5 signature generation), + * if available. + * + * \return the implementation, or 0. + */ +br_rsa_pkcs1_sign br_rsa_i62_pkcs1_sign_get(void); + +/** + * \brief Get the RSA "i62" implementation (PSS signature generation), + * if available. + * + * \return the implementation, or 0. + */ +br_rsa_pss_sign br_rsa_i62_pss_sign_get(void); + +/** + * \brief Get the RSA "i62" implementation (OAEP encryption), + * if available. + * + * \return the implementation, or 0. + */ +br_rsa_oaep_encrypt br_rsa_i62_oaep_encrypt_get(void); + +/** + * \brief Get the RSA "i62" implementation (OAEP decryption), + * if available. + * + * \return the implementation, or 0. + */ +br_rsa_oaep_decrypt br_rsa_i62_oaep_decrypt_get(void); + +/* + * RSA "i15" engine. Integers are represented as 15-bit integers, so + * the code uses only 32-bit multiplication (no 64-bit result), which + * is vastly faster (and constant-time) on the ARM Cortex M0/M0+. + */ + +/** + * \brief RSA public key engine "i15". + * + * \see br_rsa_public + * + * \param x operand to exponentiate. + * \param xlen length of the operand (in bytes). + * \param pk RSA public key. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i15_public(unsigned char *x, size_t xlen, + const br_rsa_public_key *pk); + +/** + * \brief RSA signature verification engine "i15" (PKCS#1 v1.5 signatures). + * + * \see br_rsa_pkcs1_vrfy + * + * \param x signature buffer. + * \param xlen signature length (in bytes). + * \param hash_oid encoded hash algorithm OID (or `NULL`). + * \param hash_len expected hash value length (in bytes). + * \param pk RSA public key. + * \param hash_out output buffer for the hash value. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i15_pkcs1_vrfy(const unsigned char *x, size_t xlen, + const unsigned char *hash_oid, size_t hash_len, + const br_rsa_public_key *pk, unsigned char *hash_out); + +/** + * \brief RSA signature verification engine "i15" (PSS signatures). + * + * \see br_rsa_pss_vrfy + * + * \param x signature buffer. + * \param xlen signature length (in bytes). + * \param hf_data hash function applied on the message. + * \param hf_mgf1 hash function to use with MGF1. + * \param hash hash value of the signed message. + * \param salt_len PSS salt length (in bytes). + * \param pk RSA public key. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i15_pss_vrfy(const unsigned char *x, size_t xlen, + const br_hash_class *hf_data, const br_hash_class *hf_mgf1, + const void *hash, size_t salt_len, const br_rsa_public_key *pk); + +/** + * \brief RSA private key engine "i15". + * + * \see br_rsa_private + * + * \param x operand to exponentiate. + * \param sk RSA private key. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i15_private(unsigned char *x, + const br_rsa_private_key *sk); + +/** + * \brief RSA signature generation engine "i15" (PKCS#1 v1.5 signatures). + * + * \see br_rsa_pkcs1_sign + * + * \param hash_oid encoded hash algorithm OID (or `NULL`). + * \param hash hash value. + * \param hash_len hash value length (in bytes). + * \param sk RSA private key. + * \param x output buffer for the hash value. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i15_pkcs1_sign(const unsigned char *hash_oid, + const unsigned char *hash, size_t hash_len, + const br_rsa_private_key *sk, unsigned char *x); + +/** + * \brief RSA signature generation engine "i15" (PSS signatures). + * + * \see br_rsa_pss_sign + * + * \param rng PRNG for salt generation (`NULL` if `salt_len` is zero). + * \param hf_data hash function used to hash the signed data. + * \param hf_mgf1 hash function to use with MGF1. + * \param hash hashed message. + * \param salt_len salt length (in bytes). + * \param sk RSA private key. + * \param x output buffer for the signature value. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i15_pss_sign(const br_prng_class **rng, + const br_hash_class *hf_data, const br_hash_class *hf_mgf1, + const unsigned char *hash_value, size_t salt_len, + const br_rsa_private_key *sk, unsigned char *x); + +/** + * \brief Get "default" RSA implementation (public-key operations). + * + * This returns the preferred implementation of RSA (public-key operations) + * on the current system. + * + * \return the default implementation. + */ +br_rsa_public br_rsa_public_get_default(void); + +/** + * \brief Get "default" RSA implementation (private-key operations). + * + * This returns the preferred implementation of RSA (private-key operations) + * on the current system. + * + * \return the default implementation. + */ +br_rsa_private br_rsa_private_get_default(void); + +/** + * \brief Get "default" RSA implementation (PKCS#1 v1.5 signature verification). + * + * This returns the preferred implementation of RSA (signature verification) + * on the current system. + * + * \return the default implementation. + */ +br_rsa_pkcs1_vrfy br_rsa_pkcs1_vrfy_get_default(void); + +/** + * \brief Get "default" RSA implementation (PSS signature verification). + * + * This returns the preferred implementation of RSA (signature verification) + * on the current system. + * + * \return the default implementation. + */ +br_rsa_pss_vrfy br_rsa_pss_vrfy_get_default(void); + +/** + * \brief Get "default" RSA implementation (PKCS#1 v1.5 signature generation). + * + * This returns the preferred implementation of RSA (signature generation) + * on the current system. + * + * \return the default implementation. + */ +br_rsa_pkcs1_sign br_rsa_pkcs1_sign_get_default(void); + +/** + * \brief Get "default" RSA implementation (PSS signature generation). + * + * This returns the preferred implementation of RSA (signature generation) + * on the current system. + * + * \return the default implementation. + */ +br_rsa_pss_sign br_rsa_pss_sign_get_default(void); + +/** + * \brief Get "default" RSA implementation (OAEP encryption). + * + * This returns the preferred implementation of RSA (OAEP encryption) + * on the current system. + * + * \return the default implementation. + */ +br_rsa_oaep_encrypt br_rsa_oaep_encrypt_get_default(void); + +/** + * \brief Get "default" RSA implementation (OAEP decryption). + * + * This returns the preferred implementation of RSA (OAEP decryption) + * on the current system. + * + * \return the default implementation. + */ +br_rsa_oaep_decrypt br_rsa_oaep_decrypt_get_default(void); + +/** + * \brief RSA decryption helper, for SSL/TLS. + * + * This function performs the RSA decryption for a RSA-based key exchange + * in a SSL/TLS server. The provided RSA engine is used. The `data` + * parameter points to the value to decrypt, of length `len` bytes. On + * success, the 48-byte pre-master secret is copied into `data`, starting + * at the first byte of that buffer; on error, the contents of `data` + * become indeterminate. + * + * This function first checks that the provided value length (`len`) is + * not lower than 59 bytes, and matches the RSA modulus length; if neither + * of this property is met, then this function returns 0 and the buffer + * is unmodified. + * + * Otherwise, decryption and then padding verification are performed, both + * in constant-time. A decryption error, or a bad padding, or an + * incorrect decrypted value length are reported with a returned value of + * 0; on success, 1 is returned. The caller (SSL server engine) is supposed + * to proceed with a random pre-master secret in case of error. + * + * \param core RSA private key engine. + * \param sk RSA private key. + * \param data input/output buffer. + * \param len length (in bytes) of the data to decrypt. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_ssl_decrypt(br_rsa_private core, const br_rsa_private_key *sk, + unsigned char *data, size_t len); + +/** + * \brief RSA encryption (OAEP) with the "i15" engine. + * + * \see br_rsa_oaep_encrypt + * + * \param rnd source of random bytes. + * \param dig hash function to use with MGF1. + * \param label label value (may be `NULL` if `label_len` is zero). + * \param label_len label length, in bytes. + * \param pk RSA public key. + * \param dst destination buffer. + * \param dst_max_len destination buffer length (maximum encrypted data size). + * \param src message to encrypt. + * \param src_len source message length (in bytes). + * \return encrypted message length (in bytes), or 0 on error. + */ +size_t br_rsa_i15_oaep_encrypt( + const br_prng_class **rnd, const br_hash_class *dig, + const void *label, size_t label_len, + const br_rsa_public_key *pk, + void *dst, size_t dst_max_len, + const void *src, size_t src_len); + +/** + * \brief RSA decryption (OAEP) with the "i15" engine. + * + * \see br_rsa_oaep_decrypt + * + * \param dig hash function to use with MGF1. + * \param label label value (may be `NULL` if `label_len` is zero). + * \param label_len label length, in bytes. + * \param sk RSA private key. + * \param data input/output buffer. + * \param len encrypted/decrypted message length. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i15_oaep_decrypt( + const br_hash_class *dig, const void *label, size_t label_len, + const br_rsa_private_key *sk, void *data, size_t *len); + +/** + * \brief RSA encryption (OAEP) with the "i31" engine. + * + * \see br_rsa_oaep_encrypt + * + * \param rnd source of random bytes. + * \param dig hash function to use with MGF1. + * \param label label value (may be `NULL` if `label_len` is zero). + * \param label_len label length, in bytes. + * \param pk RSA public key. + * \param dst destination buffer. + * \param dst_max_len destination buffer length (maximum encrypted data size). + * \param src message to encrypt. + * \param src_len source message length (in bytes). + * \return encrypted message length (in bytes), or 0 on error. + */ +size_t br_rsa_i31_oaep_encrypt( + const br_prng_class **rnd, const br_hash_class *dig, + const void *label, size_t label_len, + const br_rsa_public_key *pk, + void *dst, size_t dst_max_len, + const void *src, size_t src_len); + +/** + * \brief RSA decryption (OAEP) with the "i31" engine. + * + * \see br_rsa_oaep_decrypt + * + * \param dig hash function to use with MGF1. + * \param label label value (may be `NULL` if `label_len` is zero). + * \param label_len label length, in bytes. + * \param sk RSA private key. + * \param data input/output buffer. + * \param len encrypted/decrypted message length. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i31_oaep_decrypt( + const br_hash_class *dig, const void *label, size_t label_len, + const br_rsa_private_key *sk, void *data, size_t *len); + +/** + * \brief RSA encryption (OAEP) with the "i32" engine. + * + * \see br_rsa_oaep_encrypt + * + * \param rnd source of random bytes. + * \param dig hash function to use with MGF1. + * \param label label value (may be `NULL` if `label_len` is zero). + * \param label_len label length, in bytes. + * \param pk RSA public key. + * \param dst destination buffer. + * \param dst_max_len destination buffer length (maximum encrypted data size). + * \param src message to encrypt. + * \param src_len source message length (in bytes). + * \return encrypted message length (in bytes), or 0 on error. + */ +size_t br_rsa_i32_oaep_encrypt( + const br_prng_class **rnd, const br_hash_class *dig, + const void *label, size_t label_len, + const br_rsa_public_key *pk, + void *dst, size_t dst_max_len, + const void *src, size_t src_len); + +/** + * \brief RSA decryption (OAEP) with the "i32" engine. + * + * \see br_rsa_oaep_decrypt + * + * \param dig hash function to use with MGF1. + * \param label label value (may be `NULL` if `label_len` is zero). + * \param label_len label length, in bytes. + * \param sk RSA private key. + * \param data input/output buffer. + * \param len encrypted/decrypted message length. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i32_oaep_decrypt( + const br_hash_class *dig, const void *label, size_t label_len, + const br_rsa_private_key *sk, void *data, size_t *len); + +/** + * \brief RSA encryption (OAEP) with the "i62" engine. + * + * This function is defined only on architecture that offer a 64x64->128 + * opcode. Use `br_rsa_i62_oaep_encrypt_get()` to dynamically obtain a pointer + * to that function. + * + * \see br_rsa_oaep_encrypt + * + * \param rnd source of random bytes. + * \param dig hash function to use with MGF1. + * \param label label value (may be `NULL` if `label_len` is zero). + * \param label_len label length, in bytes. + * \param pk RSA public key. + * \param dst destination buffer. + * \param dst_max_len destination buffer length (maximum encrypted data size). + * \param src message to encrypt. + * \param src_len source message length (in bytes). + * \return encrypted message length (in bytes), or 0 on error. + */ +size_t br_rsa_i62_oaep_encrypt( + const br_prng_class **rnd, const br_hash_class *dig, + const void *label, size_t label_len, + const br_rsa_public_key *pk, + void *dst, size_t dst_max_len, + const void *src, size_t src_len); + +/** + * \brief RSA decryption (OAEP) with the "i62" engine. + * + * This function is defined only on architecture that offer a 64x64->128 + * opcode. Use `br_rsa_i62_oaep_decrypt_get()` to dynamically obtain a pointer + * to that function. + * + * \see br_rsa_oaep_decrypt + * + * \param dig hash function to use with MGF1. + * \param label label value (may be `NULL` if `label_len` is zero). + * \param label_len label length, in bytes. + * \param sk RSA private key. + * \param data input/output buffer. + * \param len encrypted/decrypted message length. + * \return 1 on success, 0 on error. + */ +uint32_t br_rsa_i62_oaep_decrypt( + const br_hash_class *dig, const void *label, size_t label_len, + const br_rsa_private_key *sk, void *data, size_t *len); + +/** + * \brief Get buffer size to hold RSA private key elements. + * + * This macro returns the length (in bytes) of the buffer needed to + * receive the elements of a RSA private key, as generated by one of + * the `br_rsa_*_keygen()` functions. If the provided size is a constant + * expression, then the whole macro evaluates to a constant expression. + * + * \param size target key size (modulus size, in bits) + * \return the length of the private key buffer, in bytes. + */ +#define BR_RSA_KBUF_PRIV_SIZE(size) (5 * (((size) + 15) >> 4)) + +/** + * \brief Get buffer size to hold RSA public key elements. + * + * This macro returns the length (in bytes) of the buffer needed to + * receive the elements of a RSA public key, as generated by one of + * the `br_rsa_*_keygen()` functions. If the provided size is a constant + * expression, then the whole macro evaluates to a constant expression. + * + * \param size target key size (modulus size, in bits) + * \return the length of the public key buffer, in bytes. + */ +#define BR_RSA_KBUF_PUB_SIZE(size) (4 + (((size) + 7) >> 3)) + +/** + * \brief Type for RSA key pair generator implementation. + * + * This function generates a new RSA key pair whose modulus has bit + * length `size` bits. The private key elements are written in the + * `kbuf_priv` buffer, and pointer values and length fields to these + * elements are populated in the provided private key structure `sk`. + * Similarly, the public key elements are written in `kbuf_pub`, with + * pointers and lengths set in `pk`. + * + * If `pk` is `NULL`, then `kbuf_pub` may be `NULL`, and only the + * private key is set. + * + * If `pubexp` is not zero, then its value will be used as public + * exponent. Valid RSA public exponent values are odd integers + * greater than 1. If `pubexp` is zero, then the public exponent will + * have value 3. + * + * The provided PRNG (`rng_ctx`) must have already been initialized + * and seeded. + * + * Returned value is 1 on success, 0 on error. An error is reported + * if the requested range is outside of the supported key sizes, or + * if an invalid non-zero public exponent value is provided. Supported + * range starts at 512 bits, and up to an implementation-defined + * maximum (by default 4096 bits). Note that key sizes up to 768 bits + * have been broken in practice, and sizes lower than 2048 bits are + * usually considered to be weak and should not be used. + * + * \param rng_ctx source PRNG context (already initialized) + * \param sk RSA private key structure (destination) + * \param kbuf_priv buffer for private key elements + * \param pk RSA public key structure (destination), or `NULL` + * \param kbuf_pub buffer for public key elements, or `NULL` + * \param size target RSA modulus size (in bits) + * \param pubexp public exponent to use, or zero + * \return 1 on success, 0 on error (invalid parameters) + */ +typedef uint32_t (*br_rsa_keygen)( + const br_prng_class **rng_ctx, + br_rsa_private_key *sk, void *kbuf_priv, + br_rsa_public_key *pk, void *kbuf_pub, + unsigned size, uint32_t pubexp); + +/** + * \brief RSA key pair generation with the "i15" engine. + * + * \see br_rsa_keygen + * + * \param rng_ctx source PRNG context (already initialized) + * \param sk RSA private key structure (destination) + * \param kbuf_priv buffer for private key elements + * \param pk RSA public key structure (destination), or `NULL` + * \param kbuf_pub buffer for public key elements, or `NULL` + * \param size target RSA modulus size (in bits) + * \param pubexp public exponent to use, or zero + * \return 1 on success, 0 on error (invalid parameters) + */ +uint32_t br_rsa_i15_keygen( + const br_prng_class **rng_ctx, + br_rsa_private_key *sk, void *kbuf_priv, + br_rsa_public_key *pk, void *kbuf_pub, + unsigned size, uint32_t pubexp); + +/** + * \brief RSA key pair generation with the "i31" engine. + * + * \see br_rsa_keygen + * + * \param rng_ctx source PRNG context (already initialized) + * \param sk RSA private key structure (destination) + * \param kbuf_priv buffer for private key elements + * \param pk RSA public key structure (destination), or `NULL` + * \param kbuf_pub buffer for public key elements, or `NULL` + * \param size target RSA modulus size (in bits) + * \param pubexp public exponent to use, or zero + * \return 1 on success, 0 on error (invalid parameters) + */ +uint32_t br_rsa_i31_keygen( + const br_prng_class **rng_ctx, + br_rsa_private_key *sk, void *kbuf_priv, + br_rsa_public_key *pk, void *kbuf_pub, + unsigned size, uint32_t pubexp); + +/** + * \brief RSA key pair generation with the "i62" engine. + * + * This function is defined only on architecture that offer a 64x64->128 + * opcode. Use `br_rsa_i62_keygen_get()` to dynamically obtain a pointer + * to that function. + * + * \see br_rsa_keygen + * + * \param rng_ctx source PRNG context (already initialized) + * \param sk RSA private key structure (destination) + * \param kbuf_priv buffer for private key elements + * \param pk RSA public key structure (destination), or `NULL` + * \param kbuf_pub buffer for public key elements, or `NULL` + * \param size target RSA modulus size (in bits) + * \param pubexp public exponent to use, or zero + * \return 1 on success, 0 on error (invalid parameters) + */ +uint32_t br_rsa_i62_keygen( + const br_prng_class **rng_ctx, + br_rsa_private_key *sk, void *kbuf_priv, + br_rsa_public_key *pk, void *kbuf_pub, + unsigned size, uint32_t pubexp); + +/** + * \brief Get the RSA "i62" implementation (key pair generation), + * if available. + * + * \return the implementation, or 0. + */ +br_rsa_keygen br_rsa_i62_keygen_get(void); + +/** + * \brief Get "default" RSA implementation (key pair generation). + * + * This returns the preferred implementation of RSA (key pair generation) + * on the current system. + * + * \return the default implementation. + */ +br_rsa_keygen br_rsa_keygen_get_default(void); + +/** + * \brief Type for a modulus computing function. + * + * Such a function computes the public modulus from the private key. The + * encoded modulus (unsigned big-endian) is written on `n`, and the size + * (in bytes) is returned. If `n` is `NULL`, then the size is returned but + * the modulus itself is not computed. + * + * If the key size exceeds an internal limit, 0 is returned. + * + * \param n destination buffer (or `NULL`). + * \param sk RSA private key. + * \return the modulus length (in bytes), or 0. + */ +typedef size_t (*br_rsa_compute_modulus)(void *n, const br_rsa_private_key *sk); + +/** + * \brief Recompute RSA modulus ("i15" engine). + * + * \see br_rsa_compute_modulus + * + * \param n destination buffer (or `NULL`). + * \param sk RSA private key. + * \return the modulus length (in bytes), or 0. + */ +size_t br_rsa_i15_compute_modulus(void *n, const br_rsa_private_key *sk); + +/** + * \brief Recompute RSA modulus ("i31" engine). + * + * \see br_rsa_compute_modulus + * + * \param n destination buffer (or `NULL`). + * \param sk RSA private key. + * \return the modulus length (in bytes), or 0. + */ +size_t br_rsa_i31_compute_modulus(void *n, const br_rsa_private_key *sk); + +/** + * \brief Get "default" RSA implementation (recompute modulus). + * + * This returns the preferred implementation of RSA (recompute modulus) + * on the current system. + * + * \return the default implementation. + */ +br_rsa_compute_modulus br_rsa_compute_modulus_get_default(void); + +/** + * \brief Type for a public exponent computing function. + * + * Such a function recomputes the public exponent from the private key. + * 0 is returned if any of the following occurs: + * + * - Either `p` or `q` is not equal to 3 modulo 4. + * + * - The public exponent does not fit on 32 bits. + * + * - An internal limit is exceeded. + * + * - The private key is invalid in some way. + * + * For all private keys produced by the key generator functions + * (`br_rsa_keygen` type), this function succeeds and returns the true + * public exponent. The public exponent is always an odd integer greater + * than 1. + * + * \return the public exponent, or 0. + */ +typedef uint32_t (*br_rsa_compute_pubexp)(const br_rsa_private_key *sk); + +/** + * \brief Recompute RSA public exponent ("i15" engine). + * + * \see br_rsa_compute_pubexp + * + * \return the public exponent, or 0. + */ +uint32_t br_rsa_i15_compute_pubexp(const br_rsa_private_key *sk); + +/** + * \brief Recompute RSA public exponent ("i31" engine). + * + * \see br_rsa_compute_pubexp + * + * \return the public exponent, or 0. + */ +uint32_t br_rsa_i31_compute_pubexp(const br_rsa_private_key *sk); + +/** + * \brief Get "default" RSA implementation (recompute public exponent). + * + * This returns the preferred implementation of RSA (recompute public + * exponent) on the current system. + * + * \return the default implementation. + */ +br_rsa_compute_pubexp br_rsa_compute_pubexp_get_default(void); + +/** + * \brief Type for a private exponent computing function. + * + * An RSA private key (`br_rsa_private_key`) contains two reduced + * private exponents, which are sufficient to perform private key + * operations. However, standard encoding formats for RSA private keys + * require also a copy of the complete private exponent (non-reduced), + * which this function recomputes. + * + * This function suceeds if all the following conditions hold: + * + * - Both private factors `p` and `q` are equal to 3 modulo 4. + * + * - The provided public exponent `pubexp` is correct, and, in particular, + * is odd, relatively prime to `p-1` and `q-1`, and greater than 1. + * + * - No internal storage limit is exceeded. + * + * For all private keys produced by the key generator functions + * (`br_rsa_keygen` type), this function succeeds. Note that the API + * restricts the public exponent to a maximum size of 32 bits. + * + * The encoded private exponent is written in `d` (unsigned big-endian + * convention), and the length (in bytes) is returned. If `d` is `NULL`, + * then the exponent is not written anywhere, but the length is still + * returned. On error, 0 is returned. + * + * Not all error conditions are detected when `d` is `NULL`; therefore, the + * returned value shall be checked also when actually producing the value. + * + * \param d destination buffer (or `NULL`). + * \param sk RSA private key. + * \param pubexp the public exponent. + * \return the private exponent length (in bytes), or 0. + */ +typedef size_t (*br_rsa_compute_privexp)(void *d, + const br_rsa_private_key *sk, uint32_t pubexp); + +/** + * \brief Recompute RSA private exponent ("i15" engine). + * + * \see br_rsa_compute_privexp + * + * \param d destination buffer (or `NULL`). + * \param sk RSA private key. + * \param pubexp the public exponent. + * \return the private exponent length (in bytes), or 0. + */ +size_t br_rsa_i15_compute_privexp(void *d, + const br_rsa_private_key *sk, uint32_t pubexp); + +/** + * \brief Recompute RSA private exponent ("i31" engine). + * + * \see br_rsa_compute_privexp + * + * \param d destination buffer (or `NULL`). + * \param sk RSA private key. + * \param pubexp the public exponent. + * \return the private exponent length (in bytes), or 0. + */ +size_t br_rsa_i31_compute_privexp(void *d, + const br_rsa_private_key *sk, uint32_t pubexp); + +/** + * \brief Get "default" RSA implementation (recompute private exponent). + * + * This returns the preferred implementation of RSA (recompute private + * exponent) on the current system. + * + * \return the default implementation. + */ +br_rsa_compute_privexp br_rsa_compute_privexp_get_default(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/enclave/include/bearssl/bearssl_ssl.h b/enclave/include/bearssl/bearssl_ssl.h new file mode 100644 index 0000000..f2ecc37 --- /dev/null +++ b/enclave/include/bearssl/bearssl_ssl.h @@ -0,0 +1,4296 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_SSL_H__ +#define BR_BEARSSL_SSL_H__ + +#include +#include + +#include "bearssl_block.h" +#include "bearssl_hash.h" +#include "bearssl_hmac.h" +#include "bearssl_prf.h" +#include "bearssl_rand.h" +#include "bearssl_x509.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file bearssl_ssl.h + * + * # SSL + * + * For an overview of the SSL/TLS API, see [the BearSSL Web + * site](https://www.bearssl.org/api1.html). + * + * The `BR_TLS_*` constants correspond to the standard cipher suites and + * their values in the [IANA + * registry](http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4). + * + * The `BR_ALERT_*` constants are for standard TLS alert messages. When + * a fatal alert message is sent of received, then the SSL engine context + * status is set to the sum of that alert value (an integer in the 0..255 + * range) and a fixed offset (`BR_ERR_SEND_FATAL_ALERT` for a sent alert, + * `BR_ERR_RECV_FATAL_ALERT` for a received alert). + */ + +/** \brief Optimal input buffer size. */ +#define BR_SSL_BUFSIZE_INPUT (16384 + 325) + +/** \brief Optimal output buffer size. */ +#define BR_SSL_BUFSIZE_OUTPUT (16384 + 85) + +/** \brief Optimal buffer size for monodirectional engine + (shared input/output buffer). */ +#define BR_SSL_BUFSIZE_MONO BR_SSL_BUFSIZE_INPUT + +/** \brief Optimal buffer size for bidirectional engine + (single buffer split into two separate input/output buffers). */ +#define BR_SSL_BUFSIZE_BIDI (BR_SSL_BUFSIZE_INPUT + BR_SSL_BUFSIZE_OUTPUT) + +/* + * Constants for known SSL/TLS protocol versions (SSL 3.0, TLS 1.0, TLS 1.1 + * and TLS 1.2). Note that though there is a constant for SSL 3.0, that + * protocol version is not actually supported. + */ + +/** \brief Protocol version: SSL 3.0 (unsupported). */ +#define BR_SSL30 0x0300 +/** \brief Protocol version: TLS 1.0. */ +#define BR_TLS10 0x0301 +/** \brief Protocol version: TLS 1.1. */ +#define BR_TLS11 0x0302 +/** \brief Protocol version: TLS 1.2. */ +#define BR_TLS12 0x0303 + +/* + * Error constants. They are used to report the reason why a context has + * been marked as failed. + * + * Implementation note: SSL-level error codes should be in the 1..31 + * range. The 32..63 range is for certificate decoding and validation + * errors. Received fatal alerts imply an error code in the 256..511 range. + */ + +/** \brief SSL status: no error so far (0). */ +#define BR_ERR_OK 0 + +/** \brief SSL status: caller-provided parameter is incorrect. */ +#define BR_ERR_BAD_PARAM 1 + +/** \brief SSL status: operation requested by the caller cannot be applied + with the current context state (e.g. reading data while outgoing data + is waiting to be sent). */ +#define BR_ERR_BAD_STATE 2 + +/** \brief SSL status: incoming protocol or record version is unsupported. */ +#define BR_ERR_UNSUPPORTED_VERSION 3 + +/** \brief SSL status: incoming record version does not match the expected + version. */ +#define BR_ERR_BAD_VERSION 4 + +/** \brief SSL status: incoming record length is invalid. */ +#define BR_ERR_BAD_LENGTH 5 + +/** \brief SSL status: incoming record is too large to be processed, or + buffer is too small for the handshake message to send. */ +#define BR_ERR_TOO_LARGE 6 + +/** \brief SSL status: decryption found an invalid padding, or the record + MAC is not correct. */ +#define BR_ERR_BAD_MAC 7 + +/** \brief SSL status: no initial entropy was provided, and none can be + obtained from the OS. */ +#define BR_ERR_NO_RANDOM 8 + +/** \brief SSL status: incoming record type is unknown. */ +#define BR_ERR_UNKNOWN_TYPE 9 + +/** \brief SSL status: incoming record or message has wrong type with + regards to the current engine state. */ +#define BR_ERR_UNEXPECTED 10 + +/** \brief SSL status: ChangeCipherSpec message from the peer has invalid + contents. */ +#define BR_ERR_BAD_CCS 12 + +/** \brief SSL status: alert message from the peer has invalid contents + (odd length). */ +#define BR_ERR_BAD_ALERT 13 + +/** \brief SSL status: incoming handshake message decoding failed. */ +#define BR_ERR_BAD_HANDSHAKE 14 + +/** \brief SSL status: ServerHello contains a session ID which is larger + than 32 bytes. */ +#define BR_ERR_OVERSIZED_ID 15 + +/** \brief SSL status: server wants to use a cipher suite that we did + not claim to support. This is also reported if we tried to advertise + a cipher suite that we do not support. */ +#define BR_ERR_BAD_CIPHER_SUITE 16 + +/** \brief SSL status: server wants to use a compression that we did not + claim to support. */ +#define BR_ERR_BAD_COMPRESSION 17 + +/** \brief SSL status: server's max fragment length does not match + client's. */ +#define BR_ERR_BAD_FRAGLEN 18 + +/** \brief SSL status: secure renegotiation failed. */ +#define BR_ERR_BAD_SECRENEG 19 + +/** \brief SSL status: server sent an extension type that we did not + announce, or used the same extension type several times in a single + ServerHello. */ +#define BR_ERR_EXTRA_EXTENSION 20 + +/** \brief SSL status: invalid Server Name Indication contents (when + used by the server, this extension shall be empty). */ +#define BR_ERR_BAD_SNI 21 + +/** \brief SSL status: invalid ServerHelloDone from the server (length + is not 0). */ +#define BR_ERR_BAD_HELLO_DONE 22 + +/** \brief SSL status: internal limit exceeded (e.g. server's public key + is too large). */ +#define BR_ERR_LIMIT_EXCEEDED 23 + +/** \brief SSL status: Finished message from peer does not match the + expected value. */ +#define BR_ERR_BAD_FINISHED 24 + +/** \brief SSL status: session resumption attempt with distinct version + or cipher suite. */ +#define BR_ERR_RESUME_MISMATCH 25 + +/** \brief SSL status: unsupported or invalid algorithm (ECDHE curve, + signature algorithm, hash function). */ +#define BR_ERR_INVALID_ALGORITHM 26 + +/** \brief SSL status: invalid signature (on ServerKeyExchange from + server, or in CertificateVerify from client). */ +#define BR_ERR_BAD_SIGNATURE 27 + +/** \brief SSL status: peer's public key does not have the proper type + or is not allowed for requested operation. */ +#define BR_ERR_WRONG_KEY_USAGE 28 + +/** \brief SSL status: client did not send a certificate upon request, + or the client certificate could not be validated. */ +#define BR_ERR_NO_CLIENT_AUTH 29 + +/** \brief SSL status: I/O error or premature close on underlying + transport stream. This error code is set only by the simplified + I/O API ("br_sslio_*"). */ +#define BR_ERR_IO 31 + +/** \brief SSL status: base value for a received fatal alert. + + When a fatal alert is received from the peer, the alert value + is added to this constant. */ +#define BR_ERR_RECV_FATAL_ALERT 256 + +/** \brief SSL status: base value for a sent fatal alert. + + When a fatal alert is sent to the peer, the alert value is added + to this constant. */ +#define BR_ERR_SEND_FATAL_ALERT 512 + +/* ===================================================================== */ + +/** + * \brief Decryption engine for SSL. + * + * When processing incoming records, the SSL engine will use a decryption + * engine that uses a specific context structure, and has a set of + * methods (a vtable) that follows this template. + * + * The decryption engine is responsible for applying decryption, verifying + * MAC, and keeping track of the record sequence number. + */ +typedef struct br_sslrec_in_class_ br_sslrec_in_class; +struct br_sslrec_in_class_ { + /** + * \brief Context size (in bytes). + */ + size_t context_size; + + /** + * \brief Test validity of the incoming record length. + * + * This function returns 1 if the announced length for an + * incoming record is valid, 0 otherwise, + * + * \param ctx decryption engine context. + * \param record_len incoming record length. + * \return 1 of a valid length, 0 otherwise. + */ + int (*check_length)(const br_sslrec_in_class *const *ctx, + size_t record_len); + + /** + * \brief Decrypt the incoming record. + * + * This function may assume that the record length is valid + * (it has been previously tested with `check_length()`). + * Decryption is done in place; `*len` is updated with the + * cleartext length, and the address of the first plaintext + * byte is returned. If the record is correct but empty, then + * `*len` is set to 0 and a non-`NULL` pointer is returned. + * + * On decryption/MAC error, `NULL` is returned. + * + * \param ctx decryption engine context. + * \param record_type record type (23 for application data, etc). + * \param version record version. + * \param payload address of encrypted payload. + * \param len pointer to payload length (updated). + * \return pointer to plaintext, or `NULL` on error. + */ + unsigned char *(*decrypt)(const br_sslrec_in_class **ctx, + int record_type, unsigned version, + void *payload, size_t *len); +}; + +/** + * \brief Encryption engine for SSL. + * + * When building outgoing records, the SSL engine will use an encryption + * engine that uses a specific context structure, and has a set of + * methods (a vtable) that follows this template. + * + * The encryption engine is responsible for applying encryption and MAC, + * and keeping track of the record sequence number. + */ +typedef struct br_sslrec_out_class_ br_sslrec_out_class; +struct br_sslrec_out_class_ { + /** + * \brief Context size (in bytes). + */ + size_t context_size; + + /** + * \brief Compute maximum plaintext sizes and offsets. + * + * When this function is called, the `*start` and `*end` + * values contain offsets designating the free area in the + * outgoing buffer for plaintext data; that free area is + * preceded by a 5-byte space which will receive the record + * header. + * + * The `max_plaintext()` function is responsible for adjusting + * both `*start` and `*end` to make room for any record-specific + * header, MAC, padding, and possible split. + * + * \param ctx encryption engine context. + * \param start pointer to start of plaintext offset (updated). + * \param end pointer to start of plaintext offset (updated). + */ + void (*max_plaintext)(const br_sslrec_out_class *const *ctx, + size_t *start, size_t *end); + + /** + * \brief Perform record encryption. + * + * This function encrypts the record. The plaintext address and + * length are provided. Returned value is the start of the + * encrypted record (or sequence of records, if a split was + * performed), _including_ the 5-byte header, and `*len` is + * adjusted to the total size of the record(s), there again + * including the header(s). + * + * \param ctx decryption engine context. + * \param record_type record type (23 for application data, etc). + * \param version record version. + * \param plaintext address of plaintext. + * \param len pointer to plaintext length (updated). + * \return pointer to start of built record. + */ + unsigned char *(*encrypt)(const br_sslrec_out_class **ctx, + int record_type, unsigned version, + void *plaintext, size_t *len); +}; + +/** + * \brief Context for a no-encryption engine. + * + * The no-encryption engine processes outgoing records during the initial + * handshake, before encryption is applied. + */ +typedef struct { + /** \brief No-encryption engine vtable. */ + const br_sslrec_out_class *vtable; +} br_sslrec_out_clear_context; + +/** \brief Static, constant vtable for the no-encryption engine. */ +extern const br_sslrec_out_class br_sslrec_out_clear_vtable; + +/* ===================================================================== */ + +/** + * \brief Record decryption engine class, for CBC mode. + * + * This class type extends the decryption engine class with an + * initialisation method that receives the parameters needed + * for CBC processing: block cipher implementation, block cipher key, + * HMAC parameters (hash function, key, MAC length), and IV. If the + * IV is `NULL`, then a per-record IV will be used (TLS 1.1+). + */ +typedef struct br_sslrec_in_cbc_class_ br_sslrec_in_cbc_class; +struct br_sslrec_in_cbc_class_ { + /** + * \brief Superclass, as first vtable field. + */ + br_sslrec_in_class inner; + + /** + * \brief Engine initialisation method. + * + * This method sets the vtable field in the context. + * + * \param ctx context to initialise. + * \param bc_impl block cipher implementation (CBC decryption). + * \param bc_key block cipher key. + * \param bc_key_len block cipher key length (in bytes). + * \param dig_impl hash function for HMAC. + * \param mac_key HMAC key. + * \param mac_key_len HMAC key length (in bytes). + * \param mac_out_len HMAC output length (in bytes). + * \param iv initial IV (or `NULL`). + */ + void (*init)(const br_sslrec_in_cbc_class **ctx, + const br_block_cbcdec_class *bc_impl, + const void *bc_key, size_t bc_key_len, + const br_hash_class *dig_impl, + const void *mac_key, size_t mac_key_len, size_t mac_out_len, + const void *iv); +}; + +/** + * \brief Record encryption engine class, for CBC mode. + * + * This class type extends the encryption engine class with an + * initialisation method that receives the parameters needed + * for CBC processing: block cipher implementation, block cipher key, + * HMAC parameters (hash function, key, MAC length), and IV. If the + * IV is `NULL`, then a per-record IV will be used (TLS 1.1+). + */ +typedef struct br_sslrec_out_cbc_class_ br_sslrec_out_cbc_class; +struct br_sslrec_out_cbc_class_ { + /** + * \brief Superclass, as first vtable field. + */ + br_sslrec_out_class inner; + + /** + * \brief Engine initialisation method. + * + * This method sets the vtable field in the context. + * + * \param ctx context to initialise. + * \param bc_impl block cipher implementation (CBC encryption). + * \param bc_key block cipher key. + * \param bc_key_len block cipher key length (in bytes). + * \param dig_impl hash function for HMAC. + * \param mac_key HMAC key. + * \param mac_key_len HMAC key length (in bytes). + * \param mac_out_len HMAC output length (in bytes). + * \param iv initial IV (or `NULL`). + */ + void (*init)(const br_sslrec_out_cbc_class **ctx, + const br_block_cbcenc_class *bc_impl, + const void *bc_key, size_t bc_key_len, + const br_hash_class *dig_impl, + const void *mac_key, size_t mac_key_len, size_t mac_out_len, + const void *iv); +}; + +/** + * \brief Context structure for decrypting incoming records with + * CBC + HMAC. + * + * The first field points to the vtable. The other fields are opaque + * and shall not be accessed directly. + */ +typedef struct { + /** \brief Pointer to vtable. */ + const br_sslrec_in_cbc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint64_t seq; + union { + const br_block_cbcdec_class *vtable; + br_aes_gen_cbcdec_keys aes; + br_des_gen_cbcdec_keys des; + } bc; + br_hmac_key_context mac; + size_t mac_len; + unsigned char iv[16]; + int explicit_IV; +#endif +} br_sslrec_in_cbc_context; + +/** + * \brief Static, constant vtable for record decryption with CBC. + */ +extern const br_sslrec_in_cbc_class br_sslrec_in_cbc_vtable; + +/** + * \brief Context structure for encrypting outgoing records with + * CBC + HMAC. + * + * The first field points to the vtable. The other fields are opaque + * and shall not be accessed directly. + */ +typedef struct { + /** \brief Pointer to vtable. */ + const br_sslrec_out_cbc_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + uint64_t seq; + union { + const br_block_cbcenc_class *vtable; + br_aes_gen_cbcenc_keys aes; + br_des_gen_cbcenc_keys des; + } bc; + br_hmac_key_context mac; + size_t mac_len; + unsigned char iv[16]; + int explicit_IV; +#endif +} br_sslrec_out_cbc_context; + +/** + * \brief Static, constant vtable for record encryption with CBC. + */ +extern const br_sslrec_out_cbc_class br_sslrec_out_cbc_vtable; + +/* ===================================================================== */ + +/** + * \brief Record decryption engine class, for GCM mode. + * + * This class type extends the decryption engine class with an + * initialisation method that receives the parameters needed + * for GCM processing: block cipher implementation, block cipher key, + * GHASH implementation, and 4-byte IV. + */ +typedef struct br_sslrec_in_gcm_class_ br_sslrec_in_gcm_class; +struct br_sslrec_in_gcm_class_ { + /** + * \brief Superclass, as first vtable field. + */ + br_sslrec_in_class inner; + + /** + * \brief Engine initialisation method. + * + * This method sets the vtable field in the context. + * + * \param ctx context to initialise. + * \param bc_impl block cipher implementation (CTR). + * \param key block cipher key. + * \param key_len block cipher key length (in bytes). + * \param gh_impl GHASH implementation. + * \param iv static IV (4 bytes). + */ + void (*init)(const br_sslrec_in_gcm_class **ctx, + const br_block_ctr_class *bc_impl, + const void *key, size_t key_len, + br_ghash gh_impl, + const void *iv); +}; + +/** + * \brief Record encryption engine class, for GCM mode. + * + * This class type extends the encryption engine class with an + * initialisation method that receives the parameters needed + * for GCM processing: block cipher implementation, block cipher key, + * GHASH implementation, and 4-byte IV. + */ +typedef struct br_sslrec_out_gcm_class_ br_sslrec_out_gcm_class; +struct br_sslrec_out_gcm_class_ { + /** + * \brief Superclass, as first vtable field. + */ + br_sslrec_out_class inner; + + /** + * \brief Engine initialisation method. + * + * This method sets the vtable field in the context. + * + * \param ctx context to initialise. + * \param bc_impl block cipher implementation (CTR). + * \param key block cipher key. + * \param key_len block cipher key length (in bytes). + * \param gh_impl GHASH implementation. + * \param iv static IV (4 bytes). + */ + void (*init)(const br_sslrec_out_gcm_class **ctx, + const br_block_ctr_class *bc_impl, + const void *key, size_t key_len, + br_ghash gh_impl, + const void *iv); +}; + +/** + * \brief Context structure for processing records with GCM. + * + * The same context structure is used for encrypting and decrypting. + * + * The first field points to the vtable. The other fields are opaque + * and shall not be accessed directly. + */ +typedef struct { + /** \brief Pointer to vtable. */ + union { + const void *gen; + const br_sslrec_in_gcm_class *in; + const br_sslrec_out_gcm_class *out; + } vtable; +#ifndef BR_DOXYGEN_IGNORE + uint64_t seq; + union { + const br_block_ctr_class *vtable; + br_aes_gen_ctr_keys aes; + } bc; + br_ghash gh; + unsigned char iv[4]; + unsigned char h[16]; +#endif +} br_sslrec_gcm_context; + +/** + * \brief Static, constant vtable for record decryption with GCM. + */ +extern const br_sslrec_in_gcm_class br_sslrec_in_gcm_vtable; + +/** + * \brief Static, constant vtable for record encryption with GCM. + */ +extern const br_sslrec_out_gcm_class br_sslrec_out_gcm_vtable; + +/* ===================================================================== */ + +/** + * \brief Record decryption engine class, for ChaCha20+Poly1305. + * + * This class type extends the decryption engine class with an + * initialisation method that receives the parameters needed + * for ChaCha20+Poly1305 processing: ChaCha20 implementation, + * Poly1305 implementation, key, and 12-byte IV. + */ +typedef struct br_sslrec_in_chapol_class_ br_sslrec_in_chapol_class; +struct br_sslrec_in_chapol_class_ { + /** + * \brief Superclass, as first vtable field. + */ + br_sslrec_in_class inner; + + /** + * \brief Engine initialisation method. + * + * This method sets the vtable field in the context. + * + * \param ctx context to initialise. + * \param ichacha ChaCha20 implementation. + * \param ipoly Poly1305 implementation. + * \param key secret key (32 bytes). + * \param iv static IV (12 bytes). + */ + void (*init)(const br_sslrec_in_chapol_class **ctx, + br_chacha20_run ichacha, + br_poly1305_run ipoly, + const void *key, const void *iv); +}; + +/** + * \brief Record encryption engine class, for ChaCha20+Poly1305. + * + * This class type extends the encryption engine class with an + * initialisation method that receives the parameters needed + * for ChaCha20+Poly1305 processing: ChaCha20 implementation, + * Poly1305 implementation, key, and 12-byte IV. + */ +typedef struct br_sslrec_out_chapol_class_ br_sslrec_out_chapol_class; +struct br_sslrec_out_chapol_class_ { + /** + * \brief Superclass, as first vtable field. + */ + br_sslrec_out_class inner; + + /** + * \brief Engine initialisation method. + * + * This method sets the vtable field in the context. + * + * \param ctx context to initialise. + * \param ichacha ChaCha20 implementation. + * \param ipoly Poly1305 implementation. + * \param key secret key (32 bytes). + * \param iv static IV (12 bytes). + */ + void (*init)(const br_sslrec_out_chapol_class **ctx, + br_chacha20_run ichacha, + br_poly1305_run ipoly, + const void *key, const void *iv); +}; + +/** + * \brief Context structure for processing records with ChaCha20+Poly1305. + * + * The same context structure is used for encrypting and decrypting. + * + * The first field points to the vtable. The other fields are opaque + * and shall not be accessed directly. + */ +typedef struct { + /** \brief Pointer to vtable. */ + union { + const void *gen; + const br_sslrec_in_chapol_class *in; + const br_sslrec_out_chapol_class *out; + } vtable; +#ifndef BR_DOXYGEN_IGNORE + uint64_t seq; + unsigned char key[32]; + unsigned char iv[12]; + br_chacha20_run ichacha; + br_poly1305_run ipoly; +#endif +} br_sslrec_chapol_context; + +/** + * \brief Static, constant vtable for record decryption with ChaCha20+Poly1305. + */ +extern const br_sslrec_in_chapol_class br_sslrec_in_chapol_vtable; + +/** + * \brief Static, constant vtable for record encryption with ChaCha20+Poly1305. + */ +extern const br_sslrec_out_chapol_class br_sslrec_out_chapol_vtable; + +/* ===================================================================== */ + +/** + * \brief Record decryption engine class, for CCM mode. + * + * This class type extends the decryption engine class with an + * initialisation method that receives the parameters needed + * for CCM processing: block cipher implementation, block cipher key, + * and 4-byte IV. + */ +typedef struct br_sslrec_in_ccm_class_ br_sslrec_in_ccm_class; +struct br_sslrec_in_ccm_class_ { + /** + * \brief Superclass, as first vtable field. + */ + br_sslrec_in_class inner; + + /** + * \brief Engine initialisation method. + * + * This method sets the vtable field in the context. + * + * \param ctx context to initialise. + * \param bc_impl block cipher implementation (CTR+CBC). + * \param key block cipher key. + * \param key_len block cipher key length (in bytes). + * \param iv static IV (4 bytes). + * \param tag_len tag length (in bytes) + */ + void (*init)(const br_sslrec_in_ccm_class **ctx, + const br_block_ctrcbc_class *bc_impl, + const void *key, size_t key_len, + const void *iv, size_t tag_len); +}; + +/** + * \brief Record encryption engine class, for CCM mode. + * + * This class type extends the encryption engine class with an + * initialisation method that receives the parameters needed + * for CCM processing: block cipher implementation, block cipher key, + * and 4-byte IV. + */ +typedef struct br_sslrec_out_ccm_class_ br_sslrec_out_ccm_class; +struct br_sslrec_out_ccm_class_ { + /** + * \brief Superclass, as first vtable field. + */ + br_sslrec_out_class inner; + + /** + * \brief Engine initialisation method. + * + * This method sets the vtable field in the context. + * + * \param ctx context to initialise. + * \param bc_impl block cipher implementation (CTR+CBC). + * \param key block cipher key. + * \param key_len block cipher key length (in bytes). + * \param iv static IV (4 bytes). + * \param tag_len tag length (in bytes) + */ + void (*init)(const br_sslrec_out_ccm_class **ctx, + const br_block_ctrcbc_class *bc_impl, + const void *key, size_t key_len, + const void *iv, size_t tag_len); +}; + +/** + * \brief Context structure for processing records with CCM. + * + * The same context structure is used for encrypting and decrypting. + * + * The first field points to the vtable. The other fields are opaque + * and shall not be accessed directly. + */ +typedef struct { + /** \brief Pointer to vtable. */ + union { + const void *gen; + const br_sslrec_in_ccm_class *in; + const br_sslrec_out_ccm_class *out; + } vtable; +#ifndef BR_DOXYGEN_IGNORE + uint64_t seq; + union { + const br_block_ctrcbc_class *vtable; + br_aes_gen_ctrcbc_keys aes; + } bc; + unsigned char iv[4]; + size_t tag_len; +#endif +} br_sslrec_ccm_context; + +/** + * \brief Static, constant vtable for record decryption with CCM. + */ +extern const br_sslrec_in_ccm_class br_sslrec_in_ccm_vtable; + +/** + * \brief Static, constant vtable for record encryption with CCM. + */ +extern const br_sslrec_out_ccm_class br_sslrec_out_ccm_vtable; + +/* ===================================================================== */ + +/** + * \brief Type for session parameters, to be saved for session resumption. + */ +typedef struct { + /** \brief Session ID buffer. */ + unsigned char session_id[32]; + /** \brief Session ID length (in bytes, at most 32). */ + unsigned char session_id_len; + /** \brief Protocol version. */ + uint16_t version; + /** \brief Cipher suite. */ + uint16_t cipher_suite; + /** \brief Master secret. */ + unsigned char master_secret[48]; +} br_ssl_session_parameters; + +#ifndef BR_DOXYGEN_IGNORE +/* + * Maximum number of cipher suites supported by a client or server. + */ +#define BR_MAX_CIPHER_SUITES 48 +#endif + +/** + * \brief Context structure for SSL engine. + * + * This strucuture is common to the client and server; both the client + * context (`br_ssl_client_context`) and the server context + * (`br_ssl_server_context`) include a `br_ssl_engine_context` as their + * first field. + * + * The engine context manages records, including alerts, closures, and + * transitions to new encryption/MAC algorithms. Processing of handshake + * records is delegated to externally provided code. This structure + * should not be used directly. + * + * Structure contents are opaque and shall not be accessed directly. + */ +typedef struct { +#ifndef BR_DOXYGEN_IGNORE + /* + * The error code. When non-zero, then the state is "failed" and + * no I/O may occur until reset. + */ + int err; + + /* + * Configured I/O buffers. They are either disjoint, or identical. + */ + unsigned char *ibuf, *obuf; + size_t ibuf_len, obuf_len; + + /* + * Maximum fragment length applies to outgoing records; incoming + * records can be processed as long as they fit in the input + * buffer. It is guaranteed that incoming records at least as big + * as max_frag_len can be processed. + */ + uint16_t max_frag_len; + unsigned char log_max_frag_len; + unsigned char peer_log_max_frag_len; + + /* + * Buffering management registers. + */ + size_t ixa, ixb, ixc; + size_t oxa, oxb, oxc; + unsigned char iomode; + unsigned char incrypt; + + /* + * Shutdown flag: when set to non-zero, incoming record bytes + * will not be accepted anymore. This is used after a close_notify + * has been received: afterwards, the engine no longer claims that + * it could receive bytes from the transport medium. + */ + unsigned char shutdown_recv; + + /* + * 'record_type_in' is set to the incoming record type when the + * record header has been received. + * 'record_type_out' is used to make the next outgoing record + * header when it is ready to go. + */ + unsigned char record_type_in, record_type_out; + + /* + * When a record is received, its version is extracted: + * -- if 'version_in' is 0, then it is set to the received version; + * -- otherwise, if the received version is not identical to + * the 'version_in' contents, then a failure is reported. + * + * This implements the SSL requirement that all records shall + * use the negotiated protocol version, once decided (in the + * ServerHello). It is up to the handshake handler to adjust this + * field when necessary. + */ + uint16_t version_in; + + /* + * 'version_out' is used when the next outgoing record is ready + * to go. + */ + uint16_t version_out; + + /* + * Record handler contexts. + */ + union { + const br_sslrec_in_class *vtable; + br_sslrec_in_cbc_context cbc; + br_sslrec_gcm_context gcm; + br_sslrec_chapol_context chapol; + br_sslrec_ccm_context ccm; + } in; + union { + const br_sslrec_out_class *vtable; + br_sslrec_out_clear_context clear; + br_sslrec_out_cbc_context cbc; + br_sslrec_gcm_context gcm; + br_sslrec_chapol_context chapol; + br_sslrec_ccm_context ccm; + } out; + + /* + * The "application data" flag. Value: + * 0 handshake is in process, no application data acceptable + * 1 application data can be sent and received + * 2 closing, no application data can be sent, but some + * can still be received (and discarded) + */ + unsigned char application_data; + + /* + * Context RNG. + * + * rng_init_done is initially 0. It is set to 1 when the + * basic structure of the RNG is set, and 2 when some + * entropy has been pushed in. The value 2 marks the RNG + * as "properly seeded". + * + * rng_os_rand_done is initially 0. It is set to 1 when + * some seeding from the OS or hardware has been attempted. + */ + br_hmac_drbg_context rng; + int rng_init_done; + int rng_os_rand_done; + + /* + * Supported minimum and maximum versions, and cipher suites. + */ + uint16_t version_min; + uint16_t version_max; + uint16_t suites_buf[BR_MAX_CIPHER_SUITES]; + unsigned char suites_num; + + /* + * For clients, the server name to send as a SNI extension. For + * servers, the name received in the SNI extension (if any). + */ + char server_name[256]; + + /* + * "Security parameters". These are filled by the handshake + * handler, and used when switching encryption state. + */ + unsigned char client_random[32]; + unsigned char server_random[32]; + br_ssl_session_parameters session; + + /* + * ECDHE elements: curve and point from the peer. The server also + * uses that buffer for the point to send to the client. + */ + unsigned char ecdhe_curve; + unsigned char ecdhe_point[133]; + unsigned char ecdhe_point_len; + + /* + * Secure renegotiation (RFC 5746): 'reneg' can be: + * 0 first handshake (server support is not known) + * 1 peer does not support secure renegotiation + * 2 peer supports secure renegotiation + * + * The saved_finished buffer contains the client and the + * server "Finished" values from the last handshake, in + * that order (12 bytes each). + */ + unsigned char reneg; + unsigned char saved_finished[24]; + + /* + * Behavioural flags. + */ + uint32_t flags; + + /* + * Context variables for the handshake processor. The 'pad' must + * be large enough to accommodate an RSA-encrypted pre-master + * secret, or an RSA signature; since we want to support up to + * RSA-4096, this means at least 512 bytes. (Other pad usages + * require its length to be at least 256.) + */ + struct { + uint32_t *dp; + uint32_t *rp; + const unsigned char *ip; + } cpu; + uint32_t dp_stack[32]; + uint32_t rp_stack[32]; + unsigned char pad[512]; + unsigned char *hbuf_in, *hbuf_out, *saved_hbuf_out; + size_t hlen_in, hlen_out; + void (*hsrun)(void *ctx); + + /* + * The 'action' value communicates OOB information between the + * engine and the handshake processor. + * + * From the engine: + * 0 invocation triggered by I/O + * 1 invocation triggered by explicit close + * 2 invocation triggered by explicit renegotiation + */ + unsigned char action; + + /* + * State for alert messages. Value is either 0, or the value of + * the alert level byte (level is either 1 for warning, or 2 for + * fatal; we convert all other values to 'fatal'). + */ + unsigned char alert; + + /* + * Closure flags. This flag is set when a close_notify has been + * received from the peer. + */ + unsigned char close_received; + + /* + * Multi-hasher for the handshake messages. The handshake handler + * is responsible for resetting it when appropriate. + */ + br_multihash_context mhash; + + /* + * Pointer to the X.509 engine. The engine is supposed to be + * already initialized. It is used to validate the peer's + * certificate. + */ + const br_x509_class **x509ctx; + + /* + * Certificate chain to send. This is used by both client and + * server, when they send their respective Certificate messages. + * If chain_len is 0, then chain may be NULL. + */ + const br_x509_certificate *chain; + size_t chain_len; + const unsigned char *cert_cur; + size_t cert_len; + + /* + * List of supported protocol names (ALPN extension). If unset, + * (number of names is 0), then: + * - the client sends no ALPN extension; + * - the server ignores any incoming ALPN extension. + * + * Otherwise: + * - the client sends an ALPN extension with all the names; + * - the server selects the first protocol in its list that + * the client also supports, or fails (fatal alert 120) + * if the client sends an ALPN extension and there is no + * match. + * + * The 'selected_protocol' field contains 1+n if the matching + * name has index n in the list (the value is 0 if no match was + * performed, e.g. the peer did not send an ALPN extension). + */ + const char **protocol_names; + uint16_t protocol_names_num; + uint16_t selected_protocol; + + /* + * Pointers to implementations; left to NULL for unsupported + * functions. For the raw hash functions, implementations are + * referenced from the multihasher (mhash field). + */ + br_tls_prf_impl prf10; + br_tls_prf_impl prf_sha256; + br_tls_prf_impl prf_sha384; + const br_block_cbcenc_class *iaes_cbcenc; + const br_block_cbcdec_class *iaes_cbcdec; + const br_block_ctr_class *iaes_ctr; + const br_block_ctrcbc_class *iaes_ctrcbc; + const br_block_cbcenc_class *ides_cbcenc; + const br_block_cbcdec_class *ides_cbcdec; + br_ghash ighash; + br_chacha20_run ichacha; + br_poly1305_run ipoly; + const br_sslrec_in_cbc_class *icbc_in; + const br_sslrec_out_cbc_class *icbc_out; + const br_sslrec_in_gcm_class *igcm_in; + const br_sslrec_out_gcm_class *igcm_out; + const br_sslrec_in_chapol_class *ichapol_in; + const br_sslrec_out_chapol_class *ichapol_out; + const br_sslrec_in_ccm_class *iccm_in; + const br_sslrec_out_ccm_class *iccm_out; + const br_ec_impl *iec; + br_rsa_pkcs1_vrfy irsavrfy; + br_ecdsa_vrfy iecdsa; +#endif +} br_ssl_engine_context; + +/** + * \brief Get currently defined engine behavioural flags. + * + * \param cc SSL engine context. + * \return the flags. + */ +static inline uint32_t +br_ssl_engine_get_flags(br_ssl_engine_context *cc) +{ + return cc->flags; +} + +/** + * \brief Set all engine behavioural flags. + * + * \param cc SSL engine context. + * \param flags new value for all flags. + */ +static inline void +br_ssl_engine_set_all_flags(br_ssl_engine_context *cc, uint32_t flags) +{ + cc->flags = flags; +} + +/** + * \brief Set some engine behavioural flags. + * + * The flags set in the `flags` parameter are set in the context; other + * flags are untouched. + * + * \param cc SSL engine context. + * \param flags additional set flags. + */ +static inline void +br_ssl_engine_add_flags(br_ssl_engine_context *cc, uint32_t flags) +{ + cc->flags |= flags; +} + +/** + * \brief Clear some engine behavioural flags. + * + * The flags set in the `flags` parameter are cleared from the context; other + * flags are untouched. + * + * \param cc SSL engine context. + * \param flags flags to remove. + */ +static inline void +br_ssl_engine_remove_flags(br_ssl_engine_context *cc, uint32_t flags) +{ + cc->flags &= ~flags; +} + +/** + * \brief Behavioural flag: enforce server preferences. + * + * If this flag is set, then the server will enforce its own cipher suite + * preference order; otherwise, it follows the client preferences. + */ +#define BR_OPT_ENFORCE_SERVER_PREFERENCES ((uint32_t)1 << 0) + +/** + * \brief Behavioural flag: disable renegotiation. + * + * If this flag is set, then renegotiations are rejected unconditionally: + * they won't be honoured if asked for programmatically, and requests from + * the peer are rejected. + */ +#define BR_OPT_NO_RENEGOTIATION ((uint32_t)1 << 1) + +/** + * \brief Behavioural flag: tolerate lack of client authentication. + * + * If this flag is set in a server and the server requests a client + * certificate, but the authentication fails (the client does not send + * a certificate, or the client's certificate chain cannot be validated), + * then the connection keeps on. Without this flag, a failed client + * authentication terminates the connection. + * + * Notes: + * + * - If the client's certificate can be validated and its public key is + * supported, then a wrong signature value terminates the connection + * regardless of that flag. + * + * - If using full-static ECDH, then a failure to validate the client's + * certificate prevents the handshake from succeeding. + */ +#define BR_OPT_TOLERATE_NO_CLIENT_AUTH ((uint32_t)1 << 2) + +/** + * \brief Behavioural flag: fail on application protocol mismatch. + * + * The ALPN extension ([RFC 7301](https://tools.ietf.org/html/rfc7301)) + * allows the client to send a list of application protocol names, and + * the server to select one. A mismatch is one of the following occurrences: + * + * - On the client: the client sends a list of names, the server + * responds with a protocol name which is _not_ part of the list of + * names sent by the client. + * + * - On the server: the client sends a list of names, and the server + * is also configured with a list of names, but there is no common + * protocol name between the two lists. + * + * Normal behaviour in case of mismatch is to report no matching name + * (`br_ssl_engine_get_selected_protocol()` returns `NULL`) and carry on. + * If the flag is set, then a mismatch implies a protocol failure (if + * the mismatch is detected by the server, it will send a fatal alert). + * + * Note: even with this flag, `br_ssl_engine_get_selected_protocol()` + * may still return `NULL` if the client or the server does not send an + * ALPN extension at all. + */ +#define BR_OPT_FAIL_ON_ALPN_MISMATCH ((uint32_t)1 << 3) + +/** + * \brief Set the minimum and maximum supported protocol versions. + * + * The two provided versions MUST be supported by the implementation + * (i.e. TLS 1.0, 1.1 and 1.2), and `version_max` MUST NOT be lower + * than `version_min`. + * + * \param cc SSL engine context. + * \param version_min minimum supported TLS version. + * \param version_max maximum supported TLS version. + */ +static inline void +br_ssl_engine_set_versions(br_ssl_engine_context *cc, + unsigned version_min, unsigned version_max) +{ + cc->version_min = (uint16_t)version_min; + cc->version_max = (uint16_t)version_max; +} + +/** + * \brief Set the list of cipher suites advertised by this context. + * + * The provided array is copied into the context. It is the caller + * responsibility to ensure that all provided suites will be supported + * by the context. The engine context has enough room to receive _all_ + * suites supported by the implementation. The provided array MUST NOT + * contain duplicates. + * + * If the engine is for a client, the "signaling" pseudo-cipher suite + * `TLS_FALLBACK_SCSV` can be added at the end of the list, if the + * calling application is performing a voluntary downgrade (voluntary + * downgrades are not recommended, but if such a downgrade is done, then + * adding the fallback pseudo-suite is a good idea). + * + * \param cc SSL engine context. + * \param suites cipher suites. + * \param suites_num number of cipher suites. + */ +void br_ssl_engine_set_suites(br_ssl_engine_context *cc, + const uint16_t *suites, size_t suites_num); + +/** + * \brief Set the X.509 engine. + * + * The caller shall ensure that the X.509 engine is properly initialised. + * + * \param cc SSL engine context. + * \param x509ctx X.509 certificate validation context. + */ +static inline void +br_ssl_engine_set_x509(br_ssl_engine_context *cc, const br_x509_class **x509ctx) +{ + cc->x509ctx = x509ctx; +} + +/** + * \brief Set the supported protocol names. + * + * Protocol names are part of the ALPN extension ([RFC + * 7301](https://tools.ietf.org/html/rfc7301)). Each protocol name is a + * character string, containing no more than 255 characters (256 with the + * terminating zero). When names are set, then: + * + * - The client will send an ALPN extension, containing the names. If + * the server responds with an ALPN extension, the client will verify + * that the response contains one of its name, and report that name + * through `br_ssl_engine_get_selected_protocol()`. + * + * - The server will parse incoming ALPN extension (from clients), and + * try to find a common protocol; if none is found, the connection + * is aborted with a fatal alert. On match, a response ALPN extension + * is sent, and name is reported through + * `br_ssl_engine_get_selected_protocol()`. + * + * The provided array is linked in, and must remain valid while the + * connection is live. + * + * Names MUST NOT be empty. Names MUST NOT be longer than 255 characters + * (excluding the terminating 0). + * + * \param ctx SSL engine context. + * \param names list of protocol names (zero-terminated). + * \param num number of protocol names (MUST be 1 or more). + */ +static inline void +br_ssl_engine_set_protocol_names(br_ssl_engine_context *ctx, + const char **names, size_t num) +{ + ctx->protocol_names = names; + ctx->protocol_names_num = (uint16_t)num; +} + +/** + * \brief Get the selected protocol. + * + * If this context was initialised with a non-empty list of protocol + * names, and both client and server sent ALPN extensions during the + * handshake, and a common name was found, then that name is returned. + * Otherwise, `NULL` is returned. + * + * The returned pointer is one of the pointers provided to the context + * with `br_ssl_engine_set_protocol_names()`. + * + * \return the selected protocol, or `NULL`. + */ +static inline const char * +br_ssl_engine_get_selected_protocol(br_ssl_engine_context *ctx) +{ + unsigned k; + + k = ctx->selected_protocol; + return (k == 0 || k == 0xFFFF) ? NULL : ctx->protocol_names[k - 1]; +} + +/** + * \brief Set a hash function implementation (by ID). + * + * Hash functions set with this call will be used for SSL/TLS specific + * usages, not X.509 certificate validation. Only "standard" hash functions + * may be set (MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512). If `impl` + * is `NULL`, then the hash function support is removed, not added. + * + * \param ctx SSL engine context. + * \param id hash function identifier. + * \param impl hash function implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_hash(br_ssl_engine_context *ctx, + int id, const br_hash_class *impl) +{ + br_multihash_setimpl(&ctx->mhash, id, impl); +} + +/** + * \brief Get a hash function implementation (by ID). + * + * This function retrieves a hash function implementation which was + * set with `br_ssl_engine_set_hash()`. + * + * \param ctx SSL engine context. + * \param id hash function identifier. + * \return the hash function implementation (or `NULL`). + */ +static inline const br_hash_class * +br_ssl_engine_get_hash(br_ssl_engine_context *ctx, int id) +{ + return br_multihash_getimpl(&ctx->mhash, id); +} + +/** + * \brief Set the PRF implementation (for TLS 1.0 and 1.1). + * + * This function sets (or removes, if `impl` is `NULL`) the implementation + * for the PRF used in TLS 1.0 and 1.1. + * + * \param cc SSL engine context. + * \param impl PRF implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_prf10(br_ssl_engine_context *cc, br_tls_prf_impl impl) +{ + cc->prf10 = impl; +} + +/** + * \brief Set the PRF implementation with SHA-256 (for TLS 1.2). + * + * This function sets (or removes, if `impl` is `NULL`) the implementation + * for the SHA-256 variant of the PRF used in TLS 1.2. + * + * \param cc SSL engine context. + * \param impl PRF implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_prf_sha256(br_ssl_engine_context *cc, br_tls_prf_impl impl) +{ + cc->prf_sha256 = impl; +} + +/** + * \brief Set the PRF implementation with SHA-384 (for TLS 1.2). + * + * This function sets (or removes, if `impl` is `NULL`) the implementation + * for the SHA-384 variant of the PRF used in TLS 1.2. + * + * \param cc SSL engine context. + * \param impl PRF implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_prf_sha384(br_ssl_engine_context *cc, br_tls_prf_impl impl) +{ + cc->prf_sha384 = impl; +} + +/** + * \brief Set the AES/CBC implementations. + * + * \param cc SSL engine context. + * \param impl_enc AES/CBC encryption implementation (or `NULL`). + * \param impl_dec AES/CBC decryption implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_aes_cbc(br_ssl_engine_context *cc, + const br_block_cbcenc_class *impl_enc, + const br_block_cbcdec_class *impl_dec) +{ + cc->iaes_cbcenc = impl_enc; + cc->iaes_cbcdec = impl_dec; +} + +/** + * \brief Set the "default" AES/CBC implementations. + * + * This function configures in the engine the AES implementations that + * should provide best runtime performance on the local system, while + * still being safe (in particular, constant-time). It also sets the + * handlers for CBC records. + * + * \param cc SSL engine context. + */ +void br_ssl_engine_set_default_aes_cbc(br_ssl_engine_context *cc); + +/** + * \brief Set the AES/CTR implementation. + * + * \param cc SSL engine context. + * \param impl AES/CTR encryption/decryption implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_aes_ctr(br_ssl_engine_context *cc, + const br_block_ctr_class *impl) +{ + cc->iaes_ctr = impl; +} + +/** + * \brief Set the "default" implementations for AES/GCM (AES/CTR + GHASH). + * + * This function configures in the engine the AES/CTR and GHASH + * implementation that should provide best runtime performance on the local + * system, while still being safe (in particular, constant-time). It also + * sets the handlers for GCM records. + * + * \param cc SSL engine context. + */ +void br_ssl_engine_set_default_aes_gcm(br_ssl_engine_context *cc); + +/** + * \brief Set the DES/CBC implementations. + * + * \param cc SSL engine context. + * \param impl_enc DES/CBC encryption implementation (or `NULL`). + * \param impl_dec DES/CBC decryption implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_des_cbc(br_ssl_engine_context *cc, + const br_block_cbcenc_class *impl_enc, + const br_block_cbcdec_class *impl_dec) +{ + cc->ides_cbcenc = impl_enc; + cc->ides_cbcdec = impl_dec; +} + +/** + * \brief Set the "default" DES/CBC implementations. + * + * This function configures in the engine the DES implementations that + * should provide best runtime performance on the local system, while + * still being safe (in particular, constant-time). It also sets the + * handlers for CBC records. + * + * \param cc SSL engine context. + */ +void br_ssl_engine_set_default_des_cbc(br_ssl_engine_context *cc); + +/** + * \brief Set the GHASH implementation (used in GCM mode). + * + * \param cc SSL engine context. + * \param impl GHASH implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_ghash(br_ssl_engine_context *cc, br_ghash impl) +{ + cc->ighash = impl; +} + +/** + * \brief Set the ChaCha20 implementation. + * + * \param cc SSL engine context. + * \param ichacha ChaCha20 implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_chacha20(br_ssl_engine_context *cc, + br_chacha20_run ichacha) +{ + cc->ichacha = ichacha; +} + +/** + * \brief Set the Poly1305 implementation. + * + * \param cc SSL engine context. + * \param ipoly Poly1305 implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_poly1305(br_ssl_engine_context *cc, + br_poly1305_run ipoly) +{ + cc->ipoly = ipoly; +} + +/** + * \brief Set the "default" ChaCha20 and Poly1305 implementations. + * + * This function configures in the engine the ChaCha20 and Poly1305 + * implementations that should provide best runtime performance on the + * local system, while still being safe (in particular, constant-time). + * It also sets the handlers for ChaCha20+Poly1305 records. + * + * \param cc SSL engine context. + */ +void br_ssl_engine_set_default_chapol(br_ssl_engine_context *cc); + +/** + * \brief Set the AES/CTR+CBC implementation. + * + * \param cc SSL engine context. + * \param impl AES/CTR+CBC encryption/decryption implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_aes_ctrcbc(br_ssl_engine_context *cc, + const br_block_ctrcbc_class *impl) +{ + cc->iaes_ctrcbc = impl; +} + +/** + * \brief Set the "default" implementations for AES/CCM. + * + * This function configures in the engine the AES/CTR+CBC + * implementation that should provide best runtime performance on the local + * system, while still being safe (in particular, constant-time). It also + * sets the handlers for CCM records. + * + * \param cc SSL engine context. + */ +void br_ssl_engine_set_default_aes_ccm(br_ssl_engine_context *cc); + +/** + * \brief Set the record encryption and decryption engines for CBC + HMAC. + * + * \param cc SSL engine context. + * \param impl_in record CBC decryption implementation (or `NULL`). + * \param impl_out record CBC encryption implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_cbc(br_ssl_engine_context *cc, + const br_sslrec_in_cbc_class *impl_in, + const br_sslrec_out_cbc_class *impl_out) +{ + cc->icbc_in = impl_in; + cc->icbc_out = impl_out; +} + +/** + * \brief Set the record encryption and decryption engines for GCM. + * + * \param cc SSL engine context. + * \param impl_in record GCM decryption implementation (or `NULL`). + * \param impl_out record GCM encryption implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_gcm(br_ssl_engine_context *cc, + const br_sslrec_in_gcm_class *impl_in, + const br_sslrec_out_gcm_class *impl_out) +{ + cc->igcm_in = impl_in; + cc->igcm_out = impl_out; +} + +/** + * \brief Set the record encryption and decryption engines for CCM. + * + * \param cc SSL engine context. + * \param impl_in record CCM decryption implementation (or `NULL`). + * \param impl_out record CCM encryption implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_ccm(br_ssl_engine_context *cc, + const br_sslrec_in_ccm_class *impl_in, + const br_sslrec_out_ccm_class *impl_out) +{ + cc->iccm_in = impl_in; + cc->iccm_out = impl_out; +} + +/** + * \brief Set the record encryption and decryption engines for + * ChaCha20+Poly1305. + * + * \param cc SSL engine context. + * \param impl_in record ChaCha20 decryption implementation (or `NULL`). + * \param impl_out record ChaCha20 encryption implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_chapol(br_ssl_engine_context *cc, + const br_sslrec_in_chapol_class *impl_in, + const br_sslrec_out_chapol_class *impl_out) +{ + cc->ichapol_in = impl_in; + cc->ichapol_out = impl_out; +} + +/** + * \brief Set the EC implementation. + * + * The elliptic curve implementation will be used for ECDH and ECDHE + * cipher suites, and for ECDSA support. + * + * \param cc SSL engine context. + * \param iec EC implementation (or `NULL`). + */ +static inline void +br_ssl_engine_set_ec(br_ssl_engine_context *cc, const br_ec_impl *iec) +{ + cc->iec = iec; +} + +/** + * \brief Set the "default" EC implementation. + * + * This function sets the elliptic curve implementation for ECDH and + * ECDHE cipher suites, and for ECDSA support. It selects the fastest + * implementation on the current system. + * + * \param cc SSL engine context. + */ +void br_ssl_engine_set_default_ec(br_ssl_engine_context *cc); + +/** + * \brief Get the EC implementation configured in the provided engine. + * + * \param cc SSL engine context. + * \return the EC implementation. + */ +static inline const br_ec_impl * +br_ssl_engine_get_ec(br_ssl_engine_context *cc) +{ + return cc->iec; +} + +/** + * \brief Set the RSA signature verification implementation. + * + * On the client, this is used to verify the server's signature on its + * ServerKeyExchange message (for ECDHE_RSA cipher suites). On the server, + * this is used to verify the client's CertificateVerify message (if a + * client certificate is requested, and that certificate contains a RSA key). + * + * \param cc SSL engine context. + * \param irsavrfy RSA signature verification implementation. + */ +static inline void +br_ssl_engine_set_rsavrfy(br_ssl_engine_context *cc, br_rsa_pkcs1_vrfy irsavrfy) +{ + cc->irsavrfy = irsavrfy; +} + +/** + * \brief Set the "default" RSA implementation (signature verification). + * + * This function sets the RSA implementation (signature verification) + * to the fastest implementation available on the current platform. + * + * \param cc SSL engine context. + */ +void br_ssl_engine_set_default_rsavrfy(br_ssl_engine_context *cc); + +/** + * \brief Get the RSA implementation (signature verification) configured + * in the provided engine. + * + * \param cc SSL engine context. + * \return the RSA signature verification implementation. + */ +static inline br_rsa_pkcs1_vrfy +br_ssl_engine_get_rsavrfy(br_ssl_engine_context *cc) +{ + return cc->irsavrfy; +} + +/* + * \brief Set the ECDSA implementation (signature verification). + * + * On the client, this is used to verify the server's signature on its + * ServerKeyExchange message (for ECDHE_ECDSA cipher suites). On the server, + * this is used to verify the client's CertificateVerify message (if a + * client certificate is requested, that certificate contains an EC key, + * and full-static ECDH is not used). + * + * The ECDSA implementation will use the EC core implementation configured + * in the engine context. + * + * \param cc client context. + * \param iecdsa ECDSA verification implementation. + */ +static inline void +br_ssl_engine_set_ecdsa(br_ssl_engine_context *cc, br_ecdsa_vrfy iecdsa) +{ + cc->iecdsa = iecdsa; +} + +/** + * \brief Set the "default" ECDSA implementation (signature verification). + * + * This function sets the ECDSA implementation (signature verification) + * to the fastest implementation available on the current platform. This + * call also sets the elliptic curve implementation itself, there again + * to the fastest EC implementation available. + * + * \param cc SSL engine context. + */ +void br_ssl_engine_set_default_ecdsa(br_ssl_engine_context *cc); + +/** + * \brief Get the ECDSA implementation (signature verification) configured + * in the provided engine. + * + * \param cc SSL engine context. + * \return the ECDSA signature verification implementation. + */ +static inline br_ecdsa_vrfy +br_ssl_engine_get_ecdsa(br_ssl_engine_context *cc) +{ + return cc->iecdsa; +} + +/** + * \brief Set the I/O buffer for the SSL engine. + * + * Once this call has been made, `br_ssl_client_reset()` or + * `br_ssl_server_reset()` MUST be called before using the context. + * + * The provided buffer will be used as long as the engine context is + * used. The caller is responsible for keeping it available. + * + * If `bidi` is 0, then the engine will operate in half-duplex mode + * (it won't be able to send data while there is unprocessed incoming + * data in the buffer, and it won't be able to receive data while there + * is unsent data in the buffer). The optimal buffer size in half-duplex + * mode is `BR_SSL_BUFSIZE_MONO`; if the buffer is larger, then extra + * bytes are ignored. If the buffer is smaller, then this limits the + * capacity of the engine to support all allowed record sizes. + * + * If `bidi` is 1, then the engine will split the buffer into two + * parts, for separate handling of outgoing and incoming data. This + * enables full-duplex processing, but requires more RAM. The optimal + * buffer size in full-duplex mode is `BR_SSL_BUFSIZE_BIDI`; if the + * buffer is larger, then extra bytes are ignored. If the buffer is + * smaller, then the split will favour the incoming part, so that + * interoperability is maximised. + * + * \param cc SSL engine context + * \param iobuf I/O buffer. + * \param iobuf_len I/O buffer length (in bytes). + * \param bidi non-zero for full-duplex mode. + */ +void br_ssl_engine_set_buffer(br_ssl_engine_context *cc, + void *iobuf, size_t iobuf_len, int bidi); + +/** + * \brief Set the I/O buffers for the SSL engine. + * + * Once this call has been made, `br_ssl_client_reset()` or + * `br_ssl_server_reset()` MUST be called before using the context. + * + * This function is similar to `br_ssl_engine_set_buffer()`, except + * that it enforces full-duplex mode, and the two I/O buffers are + * provided as separate chunks. + * + * The macros `BR_SSL_BUFSIZE_INPUT` and `BR_SSL_BUFSIZE_OUTPUT` + * evaluate to the optimal (maximum) sizes for the input and output + * buffer, respectively. + * + * \param cc SSL engine context + * \param ibuf input buffer. + * \param ibuf_len input buffer length (in bytes). + * \param obuf output buffer. + * \param obuf_len output buffer length (in bytes). + */ +void br_ssl_engine_set_buffers_bidi(br_ssl_engine_context *cc, + void *ibuf, size_t ibuf_len, void *obuf, size_t obuf_len); + +/** + * \brief Inject some "initial entropy" in the context. + * + * This entropy will be added to what can be obtained from the + * underlying operating system, if that OS is supported. + * + * This function may be called several times; all injected entropy chunks + * are cumulatively mixed. + * + * If entropy gathering from the OS is supported and compiled in, then this + * step is optional. Otherwise, it is mandatory to inject randomness, and + * the caller MUST take care to push (as one or several successive calls) + * enough entropy to achieve cryptographic resistance (at least 80 bits, + * preferably 128 or more). The engine will report an error if no entropy + * was provided and none can be obtained from the OS. + * + * Take care that this function cannot assess the cryptographic quality of + * the provided bytes. + * + * In all generality, "entropy" must here be considered to mean "that + * which the attacker cannot predict". If your OS/architecture does not + * have a suitable source of randomness, then you can make do with the + * combination of a large enough secret value (possibly a copy of an + * asymmetric private key that you also store on the system) AND a + * non-repeating value (e.g. current time, provided that the local clock + * cannot be reset or altered by the attacker). + * + * \param cc SSL engine context. + * \param data extra entropy to inject. + * \param len length of the extra data (in bytes). + */ +void br_ssl_engine_inject_entropy(br_ssl_engine_context *cc, + const void *data, size_t len); + +/** + * \brief Get the "server name" in this engine. + * + * For clients, this is the name provided with `br_ssl_client_reset()`; + * for servers, this is the name received from the client as part of the + * ClientHello message. If there is no such name (e.g. the client did + * not send an SNI extension) then the returned string is empty + * (returned pointer points to a byte of value 0). + * + * The returned pointer refers to a buffer inside the context, which may + * be overwritten as part of normal SSL activity (even within the same + * connection, if a renegotiation occurs). + * + * \param cc SSL engine context. + * \return the server name (possibly empty). + */ +static inline const char * +br_ssl_engine_get_server_name(const br_ssl_engine_context *cc) +{ + return cc->server_name; +} + +/** + * \brief Get the protocol version. + * + * This function returns the protocol version that is used by the + * engine. That value is set after sending (for a server) or receiving + * (for a client) the ServerHello message. + * + * \param cc SSL engine context. + * \return the protocol version. + */ +static inline unsigned +br_ssl_engine_get_version(const br_ssl_engine_context *cc) +{ + return cc->session.version; +} + +/** + * \brief Get a copy of the session parameters. + * + * The session parameters are filled during the handshake, so this + * function shall not be called before completion of the handshake. + * The initial handshake is completed when the context first allows + * application data to be injected. + * + * This function copies the current session parameters into the provided + * structure. Beware that the session parameters include the master + * secret, which is sensitive data, to handle with great care. + * + * \param cc SSL engine context. + * \param pp destination structure for the session parameters. + */ +static inline void +br_ssl_engine_get_session_parameters(const br_ssl_engine_context *cc, + br_ssl_session_parameters *pp) +{ + memcpy(pp, &cc->session, sizeof *pp); +} + +/** + * \brief Set the session parameters to the provided values. + * + * This function is meant to be used in the client, before doing a new + * handshake; a session resumption will be attempted with these + * parameters. In the server, this function has no effect. + * + * \param cc SSL engine context. + * \param pp source structure for the session parameters. + */ +static inline void +br_ssl_engine_set_session_parameters(br_ssl_engine_context *cc, + const br_ssl_session_parameters *pp) +{ + memcpy(&cc->session, pp, sizeof *pp); +} + +/** + * \brief Get identifier for the curve used for key exchange. + * + * If the cipher suite uses ECDHE, then this function returns the + * identifier for the curve used for transient parameters. This is + * defined during the course of the handshake, when the ServerKeyExchange + * is sent (on the server) or received (on the client). If the + * cipher suite does not use ECDHE (e.g. static ECDH, or RSA key + * exchange), then this value is indeterminate. + * + * @param cc SSL engine context. + * @return the ECDHE curve identifier. + */ +static inline int +br_ssl_engine_get_ecdhe_curve(br_ssl_engine_context *cc) +{ + return cc->ecdhe_curve; +} + +/** + * \brief Get the current engine state. + * + * An SSL engine (client or server) has, at any time, a state which is + * the combination of zero, one or more of these flags: + * + * - `BR_SSL_CLOSED` + * + * Engine is finished, no more I/O (until next reset). + * + * - `BR_SSL_SENDREC` + * + * Engine has some bytes to send to the peer. + * + * - `BR_SSL_RECVREC` + * + * Engine expects some bytes from the peer. + * + * - `BR_SSL_SENDAPP` + * + * Engine may receive application data to send (or flush). + * + * - `BR_SSL_RECVAPP` + * + * Engine has obtained some application data from the peer, + * that should be read by the caller. + * + * If no flag at all is set (state value is 0), then the engine is not + * fully initialised yet. + * + * The `BR_SSL_CLOSED` flag is exclusive; when it is set, no other flag + * is set. To distinguish between a normal closure and an error, use + * `br_ssl_engine_last_error()`. + * + * Generally speaking, `BR_SSL_SENDREC` and `BR_SSL_SENDAPP` are mutually + * exclusive: the input buffer, at any point, either accumulates + * plaintext data, or contains an assembled record that is being sent. + * Similarly, `BR_SSL_RECVREC` and `BR_SSL_RECVAPP` are mutually exclusive. + * This may change in a future library version. + * + * \param cc SSL engine context. + * \return the current engine state. + */ +unsigned br_ssl_engine_current_state(const br_ssl_engine_context *cc); + +/** \brief SSL engine state: closed or failed. */ +#define BR_SSL_CLOSED 0x0001 +/** \brief SSL engine state: record data is ready to be sent to the peer. */ +#define BR_SSL_SENDREC 0x0002 +/** \brief SSL engine state: engine may receive records from the peer. */ +#define BR_SSL_RECVREC 0x0004 +/** \brief SSL engine state: engine may accept application data to send. */ +#define BR_SSL_SENDAPP 0x0008 +/** \brief SSL engine state: engine has received application data. */ +#define BR_SSL_RECVAPP 0x0010 + +/** + * \brief Get the engine error indicator. + * + * The error indicator is `BR_ERR_OK` (0) if no error was encountered + * since the last call to `br_ssl_client_reset()` or + * `br_ssl_server_reset()`. Other status values are "sticky": they + * remain set, and prevent all I/O activity, until cleared. Only the + * reset calls clear the error indicator. + * + * \param cc SSL engine context. + * \return 0, or a non-zero error code. + */ +static inline int +br_ssl_engine_last_error(const br_ssl_engine_context *cc) +{ + return cc->err; +} + +/* + * There are four I/O operations, each identified by a symbolic name: + * + * sendapp inject application data in the engine + * recvapp retrieving application data from the engine + * sendrec sending records on the transport medium + * recvrec receiving records from the transport medium + * + * Terminology works thus: in a layered model where the SSL engine sits + * between the application and the network, "send" designates operations + * where bytes flow from application to network, and "recv" for the + * reverse operation. Application data (the plaintext that is to be + * conveyed through SSL) is "app", while encrypted records are "rec". + * Note that from the SSL engine point of view, "sendapp" and "recvrec" + * designate bytes that enter the engine ("inject" operation), while + * "recvapp" and "sendrec" designate bytes that exit the engine + * ("extract" operation). + * + * For the operation 'xxx', two functions are defined: + * + * br_ssl_engine_xxx_buf + * Returns a pointer and length to the buffer to use for that + * operation. '*len' is set to the number of bytes that may be read + * from the buffer (extract operation) or written to the buffer + * (inject operation). If no byte may be exchanged for that operation + * at that point, then '*len' is set to zero, and NULL is returned. + * The engine state is unmodified by this call. + * + * br_ssl_engine_xxx_ack + * Informs the engine that 'len' bytes have been read from the buffer + * (extract operation) or written to the buffer (inject operation). + * The 'len' value MUST NOT be zero. The 'len' value MUST NOT exceed + * that which was obtained from a preceding br_ssl_engine_xxx_buf() + * call. + */ + +/** + * \brief Get buffer for application data to send. + * + * If the engine is ready to accept application data to send to the + * peer, then this call returns a pointer to the buffer where such + * data shall be written, and its length is written in `*len`. + * Otherwise, `*len` is set to 0 and `NULL` is returned. + * + * \param cc SSL engine context. + * \param len receives the application data output buffer length, or 0. + * \return the application data output buffer, or `NULL`. + */ +unsigned char *br_ssl_engine_sendapp_buf( + const br_ssl_engine_context *cc, size_t *len); + +/** + * \brief Inform the engine of some new application data. + * + * After writing `len` bytes in the buffer returned by + * `br_ssl_engine_sendapp_buf()`, the application shall call this + * function to trigger any relevant processing. The `len` parameter + * MUST NOT be 0, and MUST NOT exceed the value obtained in the + * `br_ssl_engine_sendapp_buf()` call. + * + * \param cc SSL engine context. + * \param len number of bytes pushed (not zero). + */ +void br_ssl_engine_sendapp_ack(br_ssl_engine_context *cc, size_t len); + +/** + * \brief Get buffer for received application data. + * + * If the engine has received application data from the peer, hen this + * call returns a pointer to the buffer from where such data shall be + * read, and its length is written in `*len`. Otherwise, `*len` is set + * to 0 and `NULL` is returned. + * + * \param cc SSL engine context. + * \param len receives the application data input buffer length, or 0. + * \return the application data input buffer, or `NULL`. + */ +unsigned char *br_ssl_engine_recvapp_buf( + const br_ssl_engine_context *cc, size_t *len); + +/** + * \brief Acknowledge some received application data. + * + * After reading `len` bytes from the buffer returned by + * `br_ssl_engine_recvapp_buf()`, the application shall call this + * function to trigger any relevant processing. The `len` parameter + * MUST NOT be 0, and MUST NOT exceed the value obtained in the + * `br_ssl_engine_recvapp_buf()` call. + * + * \param cc SSL engine context. + * \param len number of bytes read (not zero). + */ +void br_ssl_engine_recvapp_ack(br_ssl_engine_context *cc, size_t len); + +/** + * \brief Get buffer for record data to send. + * + * If the engine has prepared some records to send to the peer, then this + * call returns a pointer to the buffer from where such data shall be + * read, and its length is written in `*len`. Otherwise, `*len` is set + * to 0 and `NULL` is returned. + * + * \param cc SSL engine context. + * \param len receives the record data output buffer length, or 0. + * \return the record data output buffer, or `NULL`. + */ +unsigned char *br_ssl_engine_sendrec_buf( + const br_ssl_engine_context *cc, size_t *len); + +/** + * \brief Acknowledge some sent record data. + * + * After reading `len` bytes from the buffer returned by + * `br_ssl_engine_sendrec_buf()`, the application shall call this + * function to trigger any relevant processing. The `len` parameter + * MUST NOT be 0, and MUST NOT exceed the value obtained in the + * `br_ssl_engine_sendrec_buf()` call. + * + * \param cc SSL engine context. + * \param len number of bytes read (not zero). + */ +void br_ssl_engine_sendrec_ack(br_ssl_engine_context *cc, size_t len); + +/** + * \brief Get buffer for incoming records. + * + * If the engine is ready to accept records from the peer, then this + * call returns a pointer to the buffer where such data shall be + * written, and its length is written in `*len`. Otherwise, `*len` is + * set to 0 and `NULL` is returned. + * + * \param cc SSL engine context. + * \param len receives the record data input buffer length, or 0. + * \return the record data input buffer, or `NULL`. + */ +unsigned char *br_ssl_engine_recvrec_buf( + const br_ssl_engine_context *cc, size_t *len); + +/** + * \brief Inform the engine of some new record data. + * + * After writing `len` bytes in the buffer returned by + * `br_ssl_engine_recvrec_buf()`, the application shall call this + * function to trigger any relevant processing. The `len` parameter + * MUST NOT be 0, and MUST NOT exceed the value obtained in the + * `br_ssl_engine_recvrec_buf()` call. + * + * \param cc SSL engine context. + * \param len number of bytes pushed (not zero). + */ +void br_ssl_engine_recvrec_ack(br_ssl_engine_context *cc, size_t len); + +/** + * \brief Flush buffered application data. + * + * If some application data has been buffered in the engine, then wrap + * it into a record and mark it for sending. If no application data has + * been buffered but the engine would be ready to accept some, AND the + * `force` parameter is non-zero, then an empty record is assembled and + * marked for sending. In all other cases, this function does nothing. + * + * Empty records are technically legal, but not all existing SSL/TLS + * implementations support them. Empty records can be useful as a + * transparent "keep-alive" mechanism to maintain some low-level + * network activity. + * + * \param cc SSL engine context. + * \param force non-zero to force sending an empty record. + */ +void br_ssl_engine_flush(br_ssl_engine_context *cc, int force); + +/** + * \brief Initiate a closure. + * + * If, at that point, the context is open and in ready state, then a + * `close_notify` alert is assembled and marked for sending; this + * triggers the closure protocol. Otherwise, no such alert is assembled. + * + * \param cc SSL engine context. + */ +void br_ssl_engine_close(br_ssl_engine_context *cc); + +/** + * \brief Initiate a renegotiation. + * + * If the engine is failed or closed, or if the peer is known not to + * support secure renegotiation (RFC 5746), or if renegotiations have + * been disabled with the `BR_OPT_NO_RENEGOTIATION` flag, or if there + * is buffered incoming application data, then this function returns 0 + * and nothing else happens. + * + * Otherwise, this function returns 1, and a renegotiation attempt is + * triggered (if a handshake is already ongoing at that point, then + * no new handshake is triggered). + * + * \param cc SSL engine context. + * \return 1 on success, 0 on error. + */ +int br_ssl_engine_renegotiate(br_ssl_engine_context *cc); + +/** + * \brief Export key material from a connected SSL engine (RFC 5705). + * + * This calls compute a secret key of arbitrary length from the master + * secret of a connected SSL engine. If the provided context is not + * currently in "application data" state (initial handshake is not + * finished, another handshake is ongoing, or the connection failed or + * was closed), then this function returns 0. Otherwise, a secret key of + * length `len` bytes is computed and written in the buffer pointed to + * by `dst`, and 1 is returned. + * + * The computed key follows the specification described in RFC 5705. + * That RFC includes two key computations, with and without a "context + * value". If `context` is `NULL`, then the variant without context is + * used; otherwise, the `context_len` bytes located at the address + * pointed to by `context` are used in the computation. Note that it + * is possible to have a "with context" key with a context length of + * zero bytes, by setting `context` to a non-`NULL` value but + * `context_len` to 0. + * + * When context bytes are used, the context length MUST NOT exceed + * 65535 bytes. + * + * \param cc SSL engine context. + * \param dst destination buffer for exported key. + * \param len exported key length (in bytes). + * \param label disambiguation label. + * \param context context value (or `NULL`). + * \param context_len context length (in bytes). + * \return 1 on success, 0 on error. + */ +int br_ssl_key_export(br_ssl_engine_context *cc, + void *dst, size_t len, const char *label, + const void *context, size_t context_len); + +/* + * Pre-declaration for the SSL client context. + */ +typedef struct br_ssl_client_context_ br_ssl_client_context; + +/** + * \brief Type for the client certificate, if requested by the server. + */ +typedef struct { + /** + * \brief Authentication type. + * + * This is either `BR_AUTH_RSA` (RSA signature), `BR_AUTH_ECDSA` + * (ECDSA signature), or `BR_AUTH_ECDH` (static ECDH key exchange). + */ + int auth_type; + + /** + * \brief Hash function for computing the CertificateVerify. + * + * This is the symbolic identifier for the hash function that + * will be used to produce the hash of handshake messages, to + * be signed into the CertificateVerify. For full static ECDH + * (client and server certificates are both EC in the same + * curve, and static ECDH is used), this value is set to -1. + * + * Take care that with TLS 1.0 and 1.1, that value MUST match + * the protocol requirements: value must be 0 (MD5+SHA-1) for + * a RSA signature, or 2 (SHA-1) for an ECDSA signature. Only + * TLS 1.2 allows for other hash functions. + */ + int hash_id; + + /** + * \brief Certificate chain to send to the server. + * + * This is an array of `br_x509_certificate` objects, each + * normally containing a DER-encoded certificate. The client + * code does not try to decode these elements. If there is no + * chain to send to the server, then this pointer shall be + * set to `NULL`. + */ + const br_x509_certificate *chain; + + /** + * \brief Certificate chain length (number of certificates). + * + * If there is no chain to send to the server, then this value + * shall be set to 0. + */ + size_t chain_len; + +} br_ssl_client_certificate; + +/* + * Note: the constants below for signatures match the TLS constants. + */ + +/** \brief Client authentication type: static ECDH. */ +#define BR_AUTH_ECDH 0 +/** \brief Client authentication type: RSA signature. */ +#define BR_AUTH_RSA 1 +/** \brief Client authentication type: ECDSA signature. */ +#define BR_AUTH_ECDSA 3 + +/** + * \brief Class type for a certificate handler (client side). + * + * A certificate handler selects a client certificate chain to send to + * the server, upon explicit request from that server. It receives + * the list of trust anchor DN from the server, and supported types + * of certificates and signatures, and returns the chain to use. It + * is also invoked to perform the corresponding private key operation + * (a signature, or an ECDH computation). + * + * The SSL client engine will first push the trust anchor DN with + * `start_name_list()`, `start_name()`, `append_name()`, `end_name()` + * and `end_name_list()`. Then it will call `choose()`, to select the + * actual chain (and signature/hash algorithms). Finally, it will call + * either `do_sign()` or `do_keyx()`, depending on the algorithm choices. + */ +typedef struct br_ssl_client_certificate_class_ br_ssl_client_certificate_class; +struct br_ssl_client_certificate_class_ { + /** + * \brief Context size (in bytes). + */ + size_t context_size; + + /** + * \brief Begin reception of a list of trust anchor names. This + * is called while parsing the incoming CertificateRequest. + * + * \param pctx certificate handler context. + */ + void (*start_name_list)(const br_ssl_client_certificate_class **pctx); + + /** + * \brief Begin reception of a new trust anchor name. + * + * The total encoded name length is provided; it is less than + * 65535 bytes. + * + * \param pctx certificate handler context. + * \param len encoded name length (in bytes). + */ + void (*start_name)(const br_ssl_client_certificate_class **pctx, + size_t len); + + /** + * \brief Receive some more bytes for the current trust anchor name. + * + * The provided reference (`data`) points to a transient buffer + * they may be reused as soon as this function returns. The chunk + * length (`len`) is never zero. + * + * \param pctx certificate handler context. + * \param data anchor name chunk. + * \param len anchor name chunk length (in bytes). + */ + void (*append_name)(const br_ssl_client_certificate_class **pctx, + const unsigned char *data, size_t len); + + /** + * \brief End current trust anchor name. + * + * This function is called when all the encoded anchor name data + * has been provided. + * + * \param pctx certificate handler context. + */ + void (*end_name)(const br_ssl_client_certificate_class **pctx); + + /** + * \brief End list of trust anchor names. + * + * This function is called when all the anchor names in the + * CertificateRequest message have been obtained. + * + * \param pctx certificate handler context. + */ + void (*end_name_list)(const br_ssl_client_certificate_class **pctx); + + /** + * \brief Select client certificate and algorithms. + * + * This callback function shall fill the provided `choices` + * structure with the selected algorithms and certificate chain. + * The `hash_id`, `chain` and `chain_len` fields must be set. If + * the client cannot or does not wish to send a certificate, + * then it shall set `chain` to `NULL` and `chain_len` to 0. + * + * The `auth_types` parameter describes the authentication types, + * signature algorithms and hash functions that are supported by + * both the client context and the server, and compatible with + * the current protocol version. This is a bit field with the + * following contents: + * + * - If RSA signatures with hash function x are supported, then + * bit x is set. + * + * - If ECDSA signatures with hash function x are supported, + * then bit 8+x is set. + * + * - If static ECDH is supported, with a RSA-signed certificate, + * then bit 16 is set. + * + * - If static ECDH is supported, with an ECDSA-signed certificate, + * then bit 17 is set. + * + * Notes: + * + * - When using TLS 1.0 or 1.1, the hash function for RSA + * signatures is always the special MD5+SHA-1 (id 0), and the + * hash function for ECDSA signatures is always SHA-1 (id 2). + * + * - When using TLS 1.2, the list of hash functions is trimmed + * down to include only hash functions that the client context + * can support. The actual server list can be obtained with + * `br_ssl_client_get_server_hashes()`; that list may be used + * to select the certificate chain to send to the server. + * + * \param pctx certificate handler context. + * \param cc SSL client context. + * \param auth_types supported authentication types and algorithms. + * \param choices destination structure for the policy choices. + */ + void (*choose)(const br_ssl_client_certificate_class **pctx, + const br_ssl_client_context *cc, uint32_t auth_types, + br_ssl_client_certificate *choices); + + /** + * \brief Perform key exchange (client part). + * + * This callback is invoked in case of a full static ECDH key + * exchange: + * + * - the cipher suite uses `ECDH_RSA` or `ECDH_ECDSA`; + * + * - the server requests a client certificate; + * + * - the client has, and sends, a client certificate that + * uses an EC key in the same curve as the server's key, + * and chooses static ECDH (the `hash_id` field in the choice + * structure was set to -1). + * + * In that situation, this callback is invoked to compute the + * client-side ECDH: the provided `data` (of length `*len` bytes) + * is the server's public key point (as decoded from its + * certificate), and the client shall multiply that point with + * its own private key, and write back the X coordinate of the + * resulting point in the same buffer, starting at offset 0. + * The `*len` value shall be modified to designate the actual + * length of the X coordinate. + * + * The callback must uphold the following: + * + * - If the input array does not have the proper length for + * an encoded curve point, then an error (0) shall be reported. + * + * - If the input array has the proper length, then processing + * MUST be constant-time, even if the data is not a valid + * encoded point. + * + * - This callback MUST check that the input point is valid. + * + * Returned value is 1 on success, 0 on error. + * + * \param pctx certificate handler context. + * \param data server public key point. + * \param len public key point length / X coordinate length. + * \return 1 on success, 0 on error. + */ + uint32_t (*do_keyx)(const br_ssl_client_certificate_class **pctx, + unsigned char *data, size_t *len); + + /** + * \brief Perform a signature (client authentication). + * + * This callback is invoked when a client certificate was sent, + * and static ECDH is not used. It shall compute a signature, + * using the client's private key, over the provided hash value + * (which is the hash of all previous handshake messages). + * + * On input, the hash value to sign is in `data`, of size + * `hv_len`; the involved hash function is identified by + * `hash_id`. The signature shall be computed and written + * back into `data`; the total size of that buffer is `len` + * bytes. + * + * This callback shall verify that the signature length does not + * exceed `len` bytes, and abstain from writing the signature if + * it does not fit. + * + * For RSA signatures, the `hash_id` may be 0, in which case + * this is the special header-less signature specified in TLS 1.0 + * and 1.1, with a 36-byte hash value. Otherwise, normal PKCS#1 + * v1.5 signatures shall be computed. + * + * For ECDSA signatures, the signature value shall use the ASN.1 + * based encoding. + * + * Returned value is the signature length (in bytes), or 0 on error. + * + * \param pctx certificate handler context. + * \param hash_id hash function identifier. + * \param hv_len hash value length (in bytes). + * \param data input/output buffer (hash value, then signature). + * \param len total buffer length (in bytes). + * \return signature length (in bytes) on success, or 0 on error. + */ + size_t (*do_sign)(const br_ssl_client_certificate_class **pctx, + int hash_id, size_t hv_len, unsigned char *data, size_t len); +}; + +/** + * \brief A single-chain RSA client certificate handler. + * + * This handler uses a single certificate chain, with a RSA + * signature. The list of trust anchor DN is ignored. + * + * Apart from the first field (vtable pointer), its contents are + * opaque and shall not be accessed directly. + */ +typedef struct { + /** \brief Pointer to vtable. */ + const br_ssl_client_certificate_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + const br_x509_certificate *chain; + size_t chain_len; + const br_rsa_private_key *sk; + br_rsa_pkcs1_sign irsasign; +#endif +} br_ssl_client_certificate_rsa_context; + +/** + * \brief A single-chain EC client certificate handler. + * + * This handler uses a single certificate chain, with a RSA + * signature. The list of trust anchor DN is ignored. + * + * This handler may support both static ECDH, and ECDSA signatures + * (either usage may be selectively disabled). + * + * Apart from the first field (vtable pointer), its contents are + * opaque and shall not be accessed directly. + */ +typedef struct { + /** \brief Pointer to vtable. */ + const br_ssl_client_certificate_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + const br_x509_certificate *chain; + size_t chain_len; + const br_ec_private_key *sk; + unsigned allowed_usages; + unsigned issuer_key_type; + const br_multihash_context *mhash; + const br_ec_impl *iec; + br_ecdsa_sign iecdsa; +#endif +} br_ssl_client_certificate_ec_context; + +/** + * \brief Context structure for a SSL client. + * + * The first field (called `eng`) is the SSL engine; all functions that + * work on a `br_ssl_engine_context` structure shall take as parameter + * a pointer to that field. The other structure fields are opaque and + * must not be accessed directly. + */ +struct br_ssl_client_context_ { + /** + * \brief The encapsulated engine context. + */ + br_ssl_engine_context eng; + +#ifndef BR_DOXYGEN_IGNORE + /* + * Minimum ClientHello length; padding with an extension (RFC + * 7685) is added if necessary to match at least that length. + * Such padding is nominally unnecessary, but it has been used + * to work around some server implementation bugs. + */ + uint16_t min_clienthello_len; + + /* + * Bit field for algoithms (hash + signature) supported by the + * server when requesting a client certificate. + */ + uint32_t hashes; + + /* + * Server's public key curve. + */ + int server_curve; + + /* + * Context for certificate handler. + */ + const br_ssl_client_certificate_class **client_auth_vtable; + + /* + * Client authentication type. + */ + unsigned char auth_type; + + /* + * Hash function to use for the client signature. This is 0xFF + * if static ECDH is used. + */ + unsigned char hash_id; + + /* + * For the core certificate handlers, thus avoiding (in most + * cases) the need for an externally provided policy context. + */ + union { + const br_ssl_client_certificate_class *vtable; + br_ssl_client_certificate_rsa_context single_rsa; + br_ssl_client_certificate_ec_context single_ec; + } client_auth; + + /* + * Implementations. + */ + br_rsa_public irsapub; +#endif +}; + +/** + * \brief Get the hash functions and signature algorithms supported by + * the server. + * + * This value is a bit field: + * + * - If RSA (PKCS#1 v1.5) is supported with hash function of ID `x`, + * then bit `x` is set (hash function ID is 0 for the special MD5+SHA-1, + * or 2 to 6 for the SHA family). + * + * - If ECDSA is supported with hash function of ID `x`, then bit `8+x` + * is set. + * + * - Newer algorithms are symbolic 16-bit identifiers that do not + * represent signature algorithm and hash function separately. If + * the TLS-level identifier is `0x0800+x` for a `x` in the 0..15 + * range, then bit `16+x` is set. + * + * "New algorithms" are currently defined only in draft documents, so + * this support is subject to possible change. Right now (early 2017), + * this maps ed25519 (EdDSA on Curve25519) to bit 23, and ed448 (EdDSA + * on Curve448) to bit 24. If the identifiers on the wire change in + * future document, then the decoding mechanism in BearSSL will be + * amended to keep mapping ed25519 and ed448 on bits 23 and 24, + * respectively. Mapping of other new algorithms (e.g. RSA/PSS) is not + * guaranteed yet. + * + * \param cc client context. + * \return the server-supported hash functions and signature algorithms. + */ +static inline uint32_t +br_ssl_client_get_server_hashes(const br_ssl_client_context *cc) +{ + return cc->hashes; +} + +/** + * \brief Get the server key curve. + * + * This function returns the ID for the curve used by the server's public + * key. This is set when the server's certificate chain is processed; + * this value is 0 if the server's key is not an EC key. + * + * \return the server's public key curve ID, or 0. + */ +static inline int +br_ssl_client_get_server_curve(const br_ssl_client_context *cc) +{ + return cc->server_curve; +} + +/* + * Each br_ssl_client_init_xxx() function sets the list of supported + * cipher suites and used implementations, as specified by the profile + * name 'xxx'. Defined profile names are: + * + * full all supported versions and suites; constant-time implementations + * TODO: add other profiles + */ + +/** + * \brief SSL client profile: full. + * + * This function initialises the provided SSL client context with + * all supported algorithms and cipher suites. It also initialises + * a companion X.509 validation engine with all supported algorithms, + * and the provided trust anchors; the X.509 engine will be used by + * the client context to validate the server's certificate. + * + * \param cc client context to initialise. + * \param xc X.509 validation context to initialise. + * \param trust_anchors trust anchors to use. + * \param trust_anchors_num number of trust anchors. + */ +void br_ssl_client_init_full(br_ssl_client_context *cc, + br_x509_minimal_context *xc, + const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num); + +/** + * \brief Clear the complete contents of a SSL client context. + * + * Everything is cleared, including the reference to the configured buffer, + * implementations, cipher suites and state. This is a preparatory step + * to assembling a custom profile. + * + * \param cc client context to clear. + */ +void br_ssl_client_zero(br_ssl_client_context *cc); + +/** + * \brief Set an externally provided client certificate handler context. + * + * The handler's methods are invoked when the server requests a client + * certificate. + * + * \param cc client context. + * \param pctx certificate handler context (pointer to its vtable field). + */ +static inline void +br_ssl_client_set_client_certificate(br_ssl_client_context *cc, + const br_ssl_client_certificate_class **pctx) +{ + cc->client_auth_vtable = pctx; +} + +/** + * \brief Set the RSA public-key operations implementation. + * + * This will be used to encrypt the pre-master secret with the server's + * RSA public key (RSA-encryption cipher suites only). + * + * \param cc client context. + * \param irsapub RSA public-key encryption implementation. + */ +static inline void +br_ssl_client_set_rsapub(br_ssl_client_context *cc, br_rsa_public irsapub) +{ + cc->irsapub = irsapub; +} + +/** + * \brief Set the "default" RSA implementation for public-key operations. + * + * This sets the RSA implementation in the client context (for encrypting + * the pre-master secret, in `TLS_RSA_*` cipher suites) to the fastest + * available on the current platform. + * + * \param cc client context. + */ +void br_ssl_client_set_default_rsapub(br_ssl_client_context *cc); + +/** + * \brief Set the minimum ClientHello length (RFC 7685 padding). + * + * If this value is set and the ClientHello would be shorter, then + * the Pad ClientHello extension will be added with enough padding bytes + * to reach the target size. Because of the extension header, the resulting + * size will sometimes be slightly more than `len` bytes if the target + * size cannot be exactly met. + * + * The target length relates to the _contents_ of the ClientHello, not + * counting its 4-byte header. For instance, if `len` is set to 512, + * then the padding will bring the ClientHello size to 516 bytes with its + * header, and 521 bytes when counting the 5-byte record header. + * + * \param cc client context. + * \param len minimum ClientHello length (in bytes). + */ +static inline void +br_ssl_client_set_min_clienthello_len(br_ssl_client_context *cc, uint16_t len) +{ + cc->min_clienthello_len = len; +} + +/** + * \brief Prepare or reset a client context for a new connection. + * + * The `server_name` parameter is used to fill the SNI extension; the + * X.509 "minimal" engine will also match that name against the server + * names included in the server's certificate. If the parameter is + * `NULL` then no SNI extension will be sent, and the X.509 "minimal" + * engine (if used for server certificate validation) will not check + * presence of any specific name in the received certificate. + * + * Therefore, setting the `server_name` to `NULL` shall be reserved + * to cases where alternate or additional methods are used to ascertain + * that the right server public key is used (e.g. a "known key" model). + * + * If `resume_session` is non-zero and the context was previously used + * then the session parameters may be reused (depending on whether the + * server previously sent a non-empty session ID, and accepts the session + * resumption). The session parameters for session resumption can also + * be set explicitly with `br_ssl_engine_set_session_parameters()`. + * + * On failure, the context is marked as failed, and this function + * returns 0. A possible failure condition is when no initial entropy + * was injected, and none could be obtained from the OS (either OS + * randomness gathering is not supported, or it failed). + * + * \param cc client context. + * \param server_name target server name, or `NULL`. + * \param resume_session non-zero to try session resumption. + * \return 0 on failure, 1 on success. + */ +int br_ssl_client_reset(br_ssl_client_context *cc, + const char *server_name, int resume_session); + +/** + * \brief Forget any session in the context. + * + * This means that the next handshake that uses this context will + * necessarily be a full handshake (this applies both to new connections + * and to renegotiations). + * + * \param cc client context. + */ +static inline void +br_ssl_client_forget_session(br_ssl_client_context *cc) +{ + cc->eng.session.session_id_len = 0; +} + +/** + * \brief Set client certificate chain and key (single RSA case). + * + * This function sets a client certificate chain, that the client will + * send to the server whenever a client certificate is requested. This + * certificate uses an RSA public key; the corresponding private key is + * invoked for authentication. Trust anchor names sent by the server are + * ignored. + * + * The provided chain and private key are linked in the client context; + * they must remain valid as long as they may be used, i.e. normally + * for the duration of the connection, since they might be invoked + * again upon renegotiations. + * + * \param cc SSL client context. + * \param chain client certificate chain (SSL order: EE comes first). + * \param chain_len client chain length (number of certificates). + * \param sk client private key. + * \param irsasign RSA signature implementation (PKCS#1 v1.5). + */ +void br_ssl_client_set_single_rsa(br_ssl_client_context *cc, + const br_x509_certificate *chain, size_t chain_len, + const br_rsa_private_key *sk, br_rsa_pkcs1_sign irsasign); + +/* + * \brief Set the client certificate chain and key (single EC case). + * + * This function sets a client certificate chain, that the client will + * send to the server whenever a client certificate is requested. This + * certificate uses an EC public key; the corresponding private key is + * invoked for authentication. Trust anchor names sent by the server are + * ignored. + * + * The provided chain and private key are linked in the client context; + * they must remain valid as long as they may be used, i.e. normally + * for the duration of the connection, since they might be invoked + * again upon renegotiations. + * + * The `allowed_usages` is a combination of usages, namely + * `BR_KEYTYPE_KEYX` and/or `BR_KEYTYPE_SIGN`. The `BR_KEYTYPE_KEYX` + * value allows full static ECDH, while the `BR_KEYTYPE_SIGN` value + * allows ECDSA signatures. If ECDSA signatures are used, then an ECDSA + * signature implementation must be provided; otherwise, the `iecdsa` + * parameter may be 0. + * + * The `cert_issuer_key_type` value is either `BR_KEYTYPE_RSA` or + * `BR_KEYTYPE_EC`; it is the type of the public key used the the CA + * that issued (signed) the client certificate. That value is used with + * full static ECDH: support of the certificate by the server depends + * on how the certificate was signed. (Note: when using TLS 1.2, this + * parameter is ignored; but its value matters for TLS 1.0 and 1.1.) + * + * \param cc server context. + * \param chain server certificate chain to send. + * \param chain_len chain length (number of certificates). + * \param sk server private key (EC). + * \param allowed_usages allowed private key usages. + * \param cert_issuer_key_type issuing CA's key type. + * \param iec EC core implementation. + * \param iecdsa ECDSA signature implementation ("asn1" format). + */ +void br_ssl_client_set_single_ec(br_ssl_client_context *cc, + const br_x509_certificate *chain, size_t chain_len, + const br_ec_private_key *sk, unsigned allowed_usages, + unsigned cert_issuer_key_type, + const br_ec_impl *iec, br_ecdsa_sign iecdsa); + +/** + * \brief Type for a "translated cipher suite", as an array of two + * 16-bit integers. + * + * The first element is the cipher suite identifier (as used on the wire). + * The second element is the concatenation of four 4-bit elements which + * characterise the cipher suite contents. In most to least significant + * order, these 4-bit elements are: + * + * - Bits 12 to 15: key exchange + server key type + * + * | val | symbolic constant | suite type | details | + * | :-- | :----------------------- | :---------- | :----------------------------------------------- | + * | 0 | `BR_SSLKEYX_RSA` | RSA | RSA key exchange, key is RSA (encryption) | + * | 1 | `BR_SSLKEYX_ECDHE_RSA` | ECDHE_RSA | ECDHE key exchange, key is RSA (signature) | + * | 2 | `BR_SSLKEYX_ECDHE_ECDSA` | ECDHE_ECDSA | ECDHE key exchange, key is EC (signature) | + * | 3 | `BR_SSLKEYX_ECDH_RSA` | ECDH_RSA | Key is EC (key exchange), cert signed with RSA | + * | 4 | `BR_SSLKEYX_ECDH_ECDSA` | ECDH_ECDSA | Key is EC (key exchange), cert signed with ECDSA | + * + * - Bits 8 to 11: symmetric encryption algorithm + * + * | val | symbolic constant | symmetric encryption | key strength (bits) | + * | :-- | :--------------------- | :------------------- | :------------------ | + * | 0 | `BR_SSLENC_3DES_CBC` | 3DES/CBC | 168 | + * | 1 | `BR_SSLENC_AES128_CBC` | AES-128/CBC | 128 | + * | 2 | `BR_SSLENC_AES256_CBC` | AES-256/CBC | 256 | + * | 3 | `BR_SSLENC_AES128_GCM` | AES-128/GCM | 128 | + * | 4 | `BR_SSLENC_AES256_GCM` | AES-256/GCM | 256 | + * | 5 | `BR_SSLENC_CHACHA20` | ChaCha20/Poly1305 | 256 | + * + * - Bits 4 to 7: MAC algorithm + * + * | val | symbolic constant | MAC type | details | + * | :-- | :----------------- | :----------- | :------------------------------------ | + * | 0 | `BR_SSLMAC_AEAD` | AEAD | No dedicated MAC (encryption is AEAD) | + * | 2 | `BR_SSLMAC_SHA1` | HMAC/SHA-1 | Value matches `br_sha1_ID` | + * | 4 | `BR_SSLMAC_SHA256` | HMAC/SHA-256 | Value matches `br_sha256_ID` | + * | 5 | `BR_SSLMAC_SHA384` | HMAC/SHA-384 | Value matches `br_sha384_ID` | + * + * - Bits 0 to 3: hash function for PRF when used with TLS-1.2 + * + * | val | symbolic constant | hash function | details | + * | :-- | :----------------- | :------------ | :----------------------------------- | + * | 4 | `BR_SSLPRF_SHA256` | SHA-256 | Value matches `br_sha256_ID` | + * | 5 | `BR_SSLPRF_SHA384` | SHA-384 | Value matches `br_sha384_ID` | + * + * For instance, cipher suite `TLS_RSA_WITH_AES_128_GCM_SHA256` has + * standard identifier 0x009C, and is translated to 0x0304, for, in + * that order: RSA key exchange (0), AES-128/GCM (3), AEAD integrity (0), + * SHA-256 in the TLS PRF (4). + */ +typedef uint16_t br_suite_translated[2]; + +#ifndef BR_DOXYGEN_IGNORE +/* + * Constants are already documented in the br_suite_translated type. + */ + +#define BR_SSLKEYX_RSA 0 +#define BR_SSLKEYX_ECDHE_RSA 1 +#define BR_SSLKEYX_ECDHE_ECDSA 2 +#define BR_SSLKEYX_ECDH_RSA 3 +#define BR_SSLKEYX_ECDH_ECDSA 4 + +#define BR_SSLENC_3DES_CBC 0 +#define BR_SSLENC_AES128_CBC 1 +#define BR_SSLENC_AES256_CBC 2 +#define BR_SSLENC_AES128_GCM 3 +#define BR_SSLENC_AES256_GCM 4 +#define BR_SSLENC_CHACHA20 5 + +#define BR_SSLMAC_AEAD 0 +#define BR_SSLMAC_SHA1 br_sha1_ID +#define BR_SSLMAC_SHA256 br_sha256_ID +#define BR_SSLMAC_SHA384 br_sha384_ID + +#define BR_SSLPRF_SHA256 br_sha256_ID +#define BR_SSLPRF_SHA384 br_sha384_ID + +#endif + +/* + * Pre-declaration for the SSL server context. + */ +typedef struct br_ssl_server_context_ br_ssl_server_context; + +/** + * \brief Type for the server policy choices, taken after analysis of + * the client message (ClientHello). + */ +typedef struct { + /** + * \brief Cipher suite to use with that client. + */ + uint16_t cipher_suite; + + /** + * \brief Hash function or algorithm for signing the ServerKeyExchange. + * + * This parameter is ignored for `TLS_RSA_*` and `TLS_ECDH_*` + * cipher suites; it is used only for `TLS_ECDHE_*` suites, in + * which the server _signs_ the ephemeral EC Diffie-Hellman + * parameters sent to the client. + * + * This identifier must be one of the following values: + * + * - `0xFF00 + id`, where `id` is a hash function identifier + * (0 for MD5+SHA-1, or 2 to 6 for one of the SHA functions); + * + * - a full 16-bit identifier, lower than `0xFF00`. + * + * If the first option is used, then the SSL engine will + * compute the hash of the data that is to be signed, with the + * designated hash function. The `do_sign()` method will be + * invoked with that hash value provided in the the `data` + * buffer. + * + * If the second option is used, then the SSL engine will NOT + * compute a hash on the data; instead, it will provide the + * to-be-signed data itself in `data`, i.e. the concatenation of + * the client random, server random, and encoded ECDH + * parameters. Furthermore, with TLS-1.2 and later, the 16-bit + * identifier will be used "as is" in the protocol, in the + * SignatureAndHashAlgorithm; for instance, `0x0401` stands for + * RSA PKCS#1 v1.5 signature (the `01`) with SHA-256 as hash + * function (the `04`). + * + * Take care that with TLS 1.0 and 1.1, the hash function is + * constrainted by the protocol: RSA signature must use + * MD5+SHA-1 (so use `0xFF00`), while ECDSA must use SHA-1 + * (`0xFF02`). Since TLS 1.0 and 1.1 don't include a + * SignatureAndHashAlgorithm field in their ServerKeyExchange + * messages, any value below `0xFF00` will be usable to send the + * raw ServerKeyExchange data to the `do_sign()` callback, but + * that callback must still follow the protocol requirements + * when generating the signature. + */ + unsigned algo_id; + + /** + * \brief Certificate chain to send to the client. + * + * This is an array of `br_x509_certificate` objects, each + * normally containing a DER-encoded certificate. The server + * code does not try to decode these elements. + */ + const br_x509_certificate *chain; + + /** + * \brief Certificate chain length (number of certificates). + */ + size_t chain_len; + +} br_ssl_server_choices; + +/** + * \brief Class type for a policy handler (server side). + * + * A policy handler selects the policy parameters for a connection + * (cipher suite and other algorithms, and certificate chain to send to + * the client); it also performs the server-side computations involving + * its permanent private key. + * + * The SSL server engine will invoke first `choose()`, once the + * ClientHello message has been received, then either `do_keyx()` + * `do_sign()`, depending on the cipher suite. + */ +typedef struct br_ssl_server_policy_class_ br_ssl_server_policy_class; +struct br_ssl_server_policy_class_ { + /** + * \brief Context size (in bytes). + */ + size_t context_size; + + /** + * \brief Select algorithms and certificates for this connection. + * + * This callback function shall fill the provided `choices` + * structure with the policy choices for this connection. This + * entails selecting the cipher suite, hash function for signing + * the ServerKeyExchange (applicable only to ECDHE cipher suites), + * and certificate chain to send. + * + * The callback receives a pointer to the server context that + * contains the relevant data. In particular, the functions + * `br_ssl_server_get_client_suites()`, + * `br_ssl_server_get_client_hashes()` and + * `br_ssl_server_get_client_curves()` can be used to obtain + * the cipher suites, hash functions and elliptic curves + * supported by both the client and server, respectively. The + * `br_ssl_engine_get_version()` and `br_ssl_engine_get_server_name()` + * functions yield the protocol version and requested server name + * (SNI), respectively. + * + * This function may modify its context structure (`pctx`) in + * arbitrary ways to keep track of its own choices. + * + * This function shall return 1 if appropriate policy choices + * could be made, or 0 if this connection cannot be pursued. + * + * \param pctx policy context. + * \param cc SSL server context. + * \param choices destination structure for the policy choices. + * \return 1 on success, 0 on error. + */ + int (*choose)(const br_ssl_server_policy_class **pctx, + const br_ssl_server_context *cc, + br_ssl_server_choices *choices); + + /** + * \brief Perform key exchange (server part). + * + * This callback is invoked to perform the server-side cryptographic + * operation for a key exchange that is not ECDHE. This callback + * uses the private key. + * + * **For RSA key exchange**, the provided `data` (of length `*len` + * bytes) shall be decrypted with the server's private key, and + * the 48-byte premaster secret copied back to the first 48 bytes + * of `data`. + * + * - The caller makes sure that `*len` is at least 59 bytes. + * + * - This callback MUST check that the provided length matches + * that of the key modulus; it shall report an error otherwise. + * + * - If the length matches that of the RSA key modulus, then + * processing MUST be constant-time, even if decryption fails, + * or the padding is incorrect, or the plaintext message length + * is not exactly 48 bytes. + * + * - This callback needs not check the two first bytes of the + * obtained pre-master secret (the caller will do that). + * + * - If an error is reported (0), then what the callback put + * in the first 48 bytes of `data` is unimportant (the caller + * will use random bytes instead). + * + * **For ECDH key exchange**, the provided `data` (of length `*len` + * bytes) is the elliptic curve point from the client. The + * callback shall multiply it with its private key, and store + * the resulting X coordinate in `data`, starting at offset 0, + * and set `*len` to the length of the X coordinate. + * + * - If the input array does not have the proper length for + * an encoded curve point, then an error (0) shall be reported. + * + * - If the input array has the proper length, then processing + * MUST be constant-time, even if the data is not a valid + * encoded point. + * + * - This callback MUST check that the input point is valid. + * + * Returned value is 1 on success, 0 on error. + * + * \param pctx policy context. + * \param data key exchange data from the client. + * \param len key exchange data length (in bytes). + * \return 1 on success, 0 on error. + */ + uint32_t (*do_keyx)(const br_ssl_server_policy_class **pctx, + unsigned char *data, size_t *len); + + /** + * \brief Perform a signature (for a ServerKeyExchange message). + * + * This callback function is invoked for ECDHE cipher suites. On + * input, the hash value or message to sign is in `data`, of + * size `hv_len`; the involved hash function or algorithm is + * identified by `algo_id`. The signature shall be computed and + * written back into `data`; the total size of that buffer is + * `len` bytes. + * + * This callback shall verify that the signature length does not + * exceed `len` bytes, and abstain from writing the signature if + * it does not fit. + * + * The `algo_id` value matches that which was written in the + * `choices` structures by the `choose()` callback. This will be + * one of the following: + * + * - `0xFF00 + id` for a hash function identifier `id`. In + * that case, the `data` buffer contains a hash value + * already computed over the data that is to be signed, + * of length `hv_len`. The `id` may be 0 to designate the + * special MD5+SHA-1 concatenation (old-style RSA signing). + * + * - Another value, lower than `0xFF00`. The `data` buffer + * then contains the raw, non-hashed data to be signed + * (concatenation of the client and server randoms and + * ECDH parameters). The callback is responsible to apply + * any relevant hashing as part of the signing process. + * + * Returned value is the signature length (in bytes), or 0 on error. + * + * \param pctx policy context. + * \param algo_id hash function / algorithm identifier. + * \param data input/output buffer (message/hash, then signature). + * \param hv_len hash value or message length (in bytes). + * \param len total buffer length (in bytes). + * \return signature length (in bytes) on success, or 0 on error. + */ + size_t (*do_sign)(const br_ssl_server_policy_class **pctx, + unsigned algo_id, + unsigned char *data, size_t hv_len, size_t len); +}; + +/** + * \brief A single-chain RSA policy handler. + * + * This policy context uses a single certificate chain, and a RSA + * private key. The context can be restricted to only signatures or + * only key exchange. + * + * Apart from the first field (vtable pointer), its contents are + * opaque and shall not be accessed directly. + */ +typedef struct { + /** \brief Pointer to vtable. */ + const br_ssl_server_policy_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + const br_x509_certificate *chain; + size_t chain_len; + const br_rsa_private_key *sk; + unsigned allowed_usages; + br_rsa_private irsacore; + br_rsa_pkcs1_sign irsasign; +#endif +} br_ssl_server_policy_rsa_context; + +/** + * \brief A single-chain EC policy handler. + * + * This policy context uses a single certificate chain, and an EC + * private key. The context can be restricted to only signatures or + * only key exchange. + * + * Due to how TLS is defined, this context must be made aware whether + * the server certificate was itself signed with RSA or ECDSA. The code + * does not try to decode the certificate to obtain that information. + * + * Apart from the first field (vtable pointer), its contents are + * opaque and shall not be accessed directly. + */ +typedef struct { + /** \brief Pointer to vtable. */ + const br_ssl_server_policy_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + const br_x509_certificate *chain; + size_t chain_len; + const br_ec_private_key *sk; + unsigned allowed_usages; + unsigned cert_issuer_key_type; + const br_multihash_context *mhash; + const br_ec_impl *iec; + br_ecdsa_sign iecdsa; +#endif +} br_ssl_server_policy_ec_context; + +/** + * \brief Class type for a session parameter cache. + * + * Session parameters are saved in the cache with `save()`, and + * retrieved with `load()`. The cache implementation can apply any + * storage and eviction strategy that it sees fit. The SSL server + * context that performs the request is provided, so that its + * functionalities may be used by the implementation (e.g. hash + * functions or random number generation). + */ +typedef struct br_ssl_session_cache_class_ br_ssl_session_cache_class; +struct br_ssl_session_cache_class_ { + /** + * \brief Context size (in bytes). + */ + size_t context_size; + + /** + * \brief Record a session. + * + * This callback should record the provided session parameters. + * The `params` structure is transient, so its contents shall + * be copied into the cache. The session ID has been randomly + * generated and always has length exactly 32 bytes. + * + * \param ctx session cache context. + * \param server_ctx SSL server context. + * \param params session parameters to save. + */ + void (*save)(const br_ssl_session_cache_class **ctx, + br_ssl_server_context *server_ctx, + const br_ssl_session_parameters *params); + + /** + * \brief Lookup a session in the cache. + * + * The session ID to lookup is in `params` and always has length + * exactly 32 bytes. If the session parameters are found in the + * cache, then the parameters shall be copied into the `params` + * structure. Returned value is 1 on successful lookup, 0 + * otherwise. + * + * \param ctx session cache context. + * \param server_ctx SSL server context. + * \param params destination for session parameters. + * \return 1 if found, 0 otherwise. + */ + int (*load)(const br_ssl_session_cache_class **ctx, + br_ssl_server_context *server_ctx, + br_ssl_session_parameters *params); +}; + +/** + * \brief Context for a basic cache system. + * + * The system stores session parameters in a buffer provided at + * initialisation time. Each entry uses exactly 100 bytes, and + * buffer sizes up to 4294967295 bytes are supported. + * + * Entries are evicted with a LRU (Least Recently Used) policy. A + * search tree is maintained to keep lookups fast even with large + * caches. + * + * Apart from the first field (vtable pointer), the structure + * contents are opaque and shall not be accessed directly. + */ +typedef struct { + /** \brief Pointer to vtable. */ + const br_ssl_session_cache_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + unsigned char *store; + size_t store_len, store_ptr; + unsigned char index_key[32]; + const br_hash_class *hash; + int init_done; + uint32_t head, tail, root; +#endif +} br_ssl_session_cache_lru; + +/** + * \brief Initialise a LRU session cache with the provided storage space. + * + * The provided storage space must remain valid as long as the cache + * is used. Arbitrary lengths are supported, up to 4294967295 bytes; + * each entry uses up exactly 100 bytes. + * + * \param cc session cache context. + * \param store storage space for cached entries. + * \param store_len storage space length (in bytes). + */ +void br_ssl_session_cache_lru_init(br_ssl_session_cache_lru *cc, + unsigned char *store, size_t store_len); + +/** + * \brief Forget an entry in an LRU session cache. + * + * The session cache context must have been initialised. The entry + * with the provided session ID (of exactly 32 bytes) is looked for + * in the cache; if located, it is disabled. + * + * \param cc session cache context. + * \param id session ID to forget. + */ +void br_ssl_session_cache_lru_forget( + br_ssl_session_cache_lru *cc, const unsigned char *id); + +/** + * \brief Context structure for a SSL server. + * + * The first field (called `eng`) is the SSL engine; all functions that + * work on a `br_ssl_engine_context` structure shall take as parameter + * a pointer to that field. The other structure fields are opaque and + * must not be accessed directly. + */ +struct br_ssl_server_context_ { + /** + * \brief The encapsulated engine context. + */ + br_ssl_engine_context eng; + +#ifndef BR_DOXYGEN_IGNORE + /* + * Maximum version from the client. + */ + uint16_t client_max_version; + + /* + * Session cache. + */ + const br_ssl_session_cache_class **cache_vtable; + + /* + * Translated cipher suites supported by the client. The list + * is trimmed to include only the cipher suites that the + * server also supports; they are in the same order as in the + * client message. + */ + br_suite_translated client_suites[BR_MAX_CIPHER_SUITES]; + unsigned char client_suites_num; + + /* + * Hash functions supported by the client, with ECDSA and RSA + * (bit mask). For hash function with id 'x', set bit index is + * x for RSA, x+8 for ECDSA. For newer algorithms, with ID + * 0x08**, bit 16+k is set for algorithm 0x0800+k. + */ + uint32_t hashes; + + /* + * Curves supported by the client (bit mask, for named curves). + */ + uint32_t curves; + + /* + * Context for chain handler. + */ + const br_ssl_server_policy_class **policy_vtable; + uint16_t sign_hash_id; + + /* + * For the core handlers, thus avoiding (in most cases) the + * need for an externally provided policy context. + */ + union { + const br_ssl_server_policy_class *vtable; + br_ssl_server_policy_rsa_context single_rsa; + br_ssl_server_policy_ec_context single_ec; + } chain_handler; + + /* + * Buffer for the ECDHE private key. + */ + unsigned char ecdhe_key[70]; + size_t ecdhe_key_len; + + /* + * Trust anchor names for client authentication. "ta_names" and + * "tas" cannot be both non-NULL. + */ + const br_x500_name *ta_names; + const br_x509_trust_anchor *tas; + size_t num_tas; + size_t cur_dn_index; + const unsigned char *cur_dn; + size_t cur_dn_len; + + /* + * Buffer for the hash value computed over all handshake messages + * prior to CertificateVerify, and identifier for the hash function. + */ + unsigned char hash_CV[64]; + size_t hash_CV_len; + int hash_CV_id; + + /* + * Server-specific implementations. + * (none for now) + */ +#endif +}; + +/* + * Each br_ssl_server_init_xxx() function sets the list of supported + * cipher suites and used implementations, as specified by the profile + * name 'xxx'. Defined profile names are: + * + * full_rsa all supported algorithm, server key type is RSA + * full_ec all supported algorithm, server key type is EC + * TODO: add other profiles + * + * Naming scheme for "minimal" profiles: min123 + * + * -- character 1: key exchange + * r = RSA + * e = ECDHE_RSA + * f = ECDHE_ECDSA + * u = ECDH_RSA + * v = ECDH_ECDSA + * -- character 2: version / PRF + * 0 = TLS 1.0 / 1.1 with MD5+SHA-1 + * 2 = TLS 1.2 with SHA-256 + * 3 = TLS 1.2 with SHA-384 + * -- character 3: encryption + * a = AES/CBC + * d = 3DES/CBC + * g = AES/GCM + * c = ChaCha20+Poly1305 + */ + +/** + * \brief SSL server profile: full_rsa. + * + * This function initialises the provided SSL server context with + * all supported algorithms and cipher suites that rely on a RSA + * key pair. + * + * \param cc server context to initialise. + * \param chain server certificate chain. + * \param chain_len certificate chain length (number of certificate). + * \param sk RSA private key. + */ +void br_ssl_server_init_full_rsa(br_ssl_server_context *cc, + const br_x509_certificate *chain, size_t chain_len, + const br_rsa_private_key *sk); + +/** + * \brief SSL server profile: full_ec. + * + * This function initialises the provided SSL server context with + * all supported algorithms and cipher suites that rely on an EC + * key pair. + * + * The key type of the CA that issued the server's certificate must + * be provided, since it matters for ECDH cipher suites (ECDH_RSA + * suites require a RSA-powered CA). The key type is either + * `BR_KEYTYPE_RSA` or `BR_KEYTYPE_EC`. + * + * \param cc server context to initialise. + * \param chain server certificate chain. + * \param chain_len chain length (number of certificates). + * \param cert_issuer_key_type certificate issuer's key type. + * \param sk EC private key. + */ +void br_ssl_server_init_full_ec(br_ssl_server_context *cc, + const br_x509_certificate *chain, size_t chain_len, + unsigned cert_issuer_key_type, const br_ec_private_key *sk); + +/** + * \brief SSL server profile: minr2g. + * + * This profile uses only TLS_RSA_WITH_AES_128_GCM_SHA256. Server key is + * RSA, and RSA key exchange is used (not forward secure, but uses little + * CPU in the client). + * + * \param cc server context to initialise. + * \param chain server certificate chain. + * \param chain_len certificate chain length (number of certificate). + * \param sk RSA private key. + */ +void br_ssl_server_init_minr2g(br_ssl_server_context *cc, + const br_x509_certificate *chain, size_t chain_len, + const br_rsa_private_key *sk); + +/** + * \brief SSL server profile: mine2g. + * + * This profile uses only TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256. Server key + * is RSA, and ECDHE key exchange is used. This suite provides forward + * security, with a higher CPU expense on the client, and a somewhat + * larger code footprint (compared to "minr2g"). + * + * \param cc server context to initialise. + * \param chain server certificate chain. + * \param chain_len certificate chain length (number of certificate). + * \param sk RSA private key. + */ +void br_ssl_server_init_mine2g(br_ssl_server_context *cc, + const br_x509_certificate *chain, size_t chain_len, + const br_rsa_private_key *sk); + +/** + * \brief SSL server profile: minf2g. + * + * This profile uses only TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256. + * Server key is EC, and ECDHE key exchange is used. This suite provides + * forward security, with a higher CPU expense on the client and server + * (by a factor of about 3 to 4), and a somewhat larger code footprint + * (compared to "minu2g" and "minv2g"). + * + * \param cc server context to initialise. + * \param chain server certificate chain. + * \param chain_len certificate chain length (number of certificate). + * \param sk EC private key. + */ +void br_ssl_server_init_minf2g(br_ssl_server_context *cc, + const br_x509_certificate *chain, size_t chain_len, + const br_ec_private_key *sk); + +/** + * \brief SSL server profile: minu2g. + * + * This profile uses only TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256. + * Server key is EC, and ECDH key exchange is used; the issuing CA used + * a RSA key. + * + * The "minu2g" and "minv2g" profiles do not provide forward secrecy, + * but are the lightest on the server (for CPU usage), and are rather + * inexpensive on the client as well. + * + * \param cc server context to initialise. + * \param chain server certificate chain. + * \param chain_len certificate chain length (number of certificate). + * \param sk EC private key. + */ +void br_ssl_server_init_minu2g(br_ssl_server_context *cc, + const br_x509_certificate *chain, size_t chain_len, + const br_ec_private_key *sk); + +/** + * \brief SSL server profile: minv2g. + * + * This profile uses only TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256. + * Server key is EC, and ECDH key exchange is used; the issuing CA used + * an EC key. + * + * The "minu2g" and "minv2g" profiles do not provide forward secrecy, + * but are the lightest on the server (for CPU usage), and are rather + * inexpensive on the client as well. + * + * \param cc server context to initialise. + * \param chain server certificate chain. + * \param chain_len certificate chain length (number of certificate). + * \param sk EC private key. + */ +void br_ssl_server_init_minv2g(br_ssl_server_context *cc, + const br_x509_certificate *chain, size_t chain_len, + const br_ec_private_key *sk); + +/** + * \brief SSL server profile: mine2c. + * + * This profile uses only TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256. + * Server key is RSA, and ECDHE key exchange is used. This suite + * provides forward security. + * + * \param cc server context to initialise. + * \param chain server certificate chain. + * \param chain_len certificate chain length (number of certificate). + * \param sk RSA private key. + */ +void br_ssl_server_init_mine2c(br_ssl_server_context *cc, + const br_x509_certificate *chain, size_t chain_len, + const br_rsa_private_key *sk); + +/** + * \brief SSL server profile: minf2c. + * + * This profile uses only TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256. + * Server key is EC, and ECDHE key exchange is used. This suite provides + * forward security. + * + * \param cc server context to initialise. + * \param chain server certificate chain. + * \param chain_len certificate chain length (number of certificate). + * \param sk EC private key. + */ +void br_ssl_server_init_minf2c(br_ssl_server_context *cc, + const br_x509_certificate *chain, size_t chain_len, + const br_ec_private_key *sk); + +/** + * \brief Get the supported client suites. + * + * This function shall be called only after the ClientHello has been + * processed, typically from the policy engine. The returned array + * contains the cipher suites that are supported by both the client + * and the server; these suites are in client preference order, unless + * the `BR_OPT_ENFORCE_SERVER_PREFERENCES` flag was set, in which case + * they are in server preference order. + * + * The suites are _translated_, which means that each suite is given + * as two 16-bit integers: the standard suite identifier, and its + * translated version, broken down into its individual components, + * as explained with the `br_suite_translated` type. + * + * The returned array is allocated in the context and will be rewritten + * by each handshake. + * + * \param cc server context. + * \param num receives the array size (number of suites). + * \return the translated common cipher suites, in preference order. + */ +static inline const br_suite_translated * +br_ssl_server_get_client_suites(const br_ssl_server_context *cc, size_t *num) +{ + *num = cc->client_suites_num; + return cc->client_suites; +} + +/** + * \brief Get the hash functions and signature algorithms supported by + * the client. + * + * This value is a bit field: + * + * - If RSA (PKCS#1 v1.5) is supported with hash function of ID `x`, + * then bit `x` is set (hash function ID is 0 for the special MD5+SHA-1, + * or 2 to 6 for the SHA family). + * + * - If ECDSA is supported with hash function of ID `x`, then bit `8+x` + * is set. + * + * - Newer algorithms are symbolic 16-bit identifiers that do not + * represent signature algorithm and hash function separately. If + * the TLS-level identifier is `0x0800+x` for a `x` in the 0..15 + * range, then bit `16+x` is set. + * + * "New algorithms" are currently defined only in draft documents, so + * this support is subject to possible change. Right now (early 2017), + * this maps ed25519 (EdDSA on Curve25519) to bit 23, and ed448 (EdDSA + * on Curve448) to bit 24. If the identifiers on the wire change in + * future document, then the decoding mechanism in BearSSL will be + * amended to keep mapping ed25519 and ed448 on bits 23 and 24, + * respectively. Mapping of other new algorithms (e.g. RSA/PSS) is not + * guaranteed yet. + * + * \param cc server context. + * \return the client-supported hash functions and signature algorithms. + */ +static inline uint32_t +br_ssl_server_get_client_hashes(const br_ssl_server_context *cc) +{ + return cc->hashes; +} + +/** + * \brief Get the elliptic curves supported by the client. + * + * This is a bit field (bit x is set if curve of ID x is supported). + * + * \param cc server context. + * \return the client-supported elliptic curves. + */ +static inline uint32_t +br_ssl_server_get_client_curves(const br_ssl_server_context *cc) +{ + return cc->curves; +} + +/** + * \brief Clear the complete contents of a SSL server context. + * + * Everything is cleared, including the reference to the configured buffer, + * implementations, cipher suites and state. This is a preparatory step + * to assembling a custom profile. + * + * \param cc server context to clear. + */ +void br_ssl_server_zero(br_ssl_server_context *cc); + +/** + * \brief Set an externally provided policy context. + * + * The policy context's methods are invoked to decide the cipher suite + * and certificate chain, and to perform operations involving the server's + * private key. + * + * \param cc server context. + * \param pctx policy context (pointer to its vtable field). + */ +static inline void +br_ssl_server_set_policy(br_ssl_server_context *cc, + const br_ssl_server_policy_class **pctx) +{ + cc->policy_vtable = pctx; +} + +/** + * \brief Set the server certificate chain and key (single RSA case). + * + * This function uses a policy context included in the server context. + * It configures use of a single server certificate chain with a RSA + * private key. The `allowed_usages` is a combination of usages, namely + * `BR_KEYTYPE_KEYX` and/or `BR_KEYTYPE_SIGN`; this enables or disables + * the corresponding cipher suites (i.e. `TLS_RSA_*` use the RSA key for + * key exchange, while `TLS_ECDHE_RSA_*` use the RSA key for signatures). + * + * \param cc server context. + * \param chain server certificate chain to send to the client. + * \param chain_len chain length (number of certificates). + * \param sk server private key (RSA). + * \param allowed_usages allowed private key usages. + * \param irsacore RSA core implementation. + * \param irsasign RSA signature implementation (PKCS#1 v1.5). + */ +void br_ssl_server_set_single_rsa(br_ssl_server_context *cc, + const br_x509_certificate *chain, size_t chain_len, + const br_rsa_private_key *sk, unsigned allowed_usages, + br_rsa_private irsacore, br_rsa_pkcs1_sign irsasign); + +/** + * \brief Set the server certificate chain and key (single EC case). + * + * This function uses a policy context included in the server context. + * It configures use of a single server certificate chain with an EC + * private key. The `allowed_usages` is a combination of usages, namely + * `BR_KEYTYPE_KEYX` and/or `BR_KEYTYPE_SIGN`; this enables or disables + * the corresponding cipher suites (i.e. `TLS_ECDH_*` use the EC key for + * key exchange, while `TLS_ECDHE_ECDSA_*` use the EC key for signatures). + * + * In order to support `TLS_ECDH_*` cipher suites (non-ephemeral ECDH), + * the algorithm type of the key used by the issuing CA to sign the + * server's certificate must be provided, as `cert_issuer_key_type` + * parameter (this value is either `BR_KEYTYPE_RSA` or `BR_KEYTYPE_EC`). + * + * \param cc server context. + * \param chain server certificate chain to send. + * \param chain_len chain length (number of certificates). + * \param sk server private key (EC). + * \param allowed_usages allowed private key usages. + * \param cert_issuer_key_type issuing CA's key type. + * \param iec EC core implementation. + * \param iecdsa ECDSA signature implementation ("asn1" format). + */ +void br_ssl_server_set_single_ec(br_ssl_server_context *cc, + const br_x509_certificate *chain, size_t chain_len, + const br_ec_private_key *sk, unsigned allowed_usages, + unsigned cert_issuer_key_type, + const br_ec_impl *iec, br_ecdsa_sign iecdsa); + +/** + * \brief Activate client certificate authentication. + * + * The trust anchor encoded X.500 names (DN) to send to the client are + * provided. A client certificate will be requested and validated through + * the X.509 validator configured in the SSL engine. If `num` is 0, then + * client certificate authentication is disabled. + * + * If the client does not send a certificate, or on validation failure, + * the handshake aborts. Unauthenticated clients can be tolerated by + * setting the `BR_OPT_TOLERATE_NO_CLIENT_AUTH` flag. + * + * The provided array is linked in, not copied, so that pointer must + * remain valid as long as anchor names may be used. + * + * \param cc server context. + * \param ta_names encoded trust anchor names. + * \param num number of encoded trust anchor names. + */ +static inline void +br_ssl_server_set_trust_anchor_names(br_ssl_server_context *cc, + const br_x500_name *ta_names, size_t num) +{ + cc->ta_names = ta_names; + cc->tas = NULL; + cc->num_tas = num; +} + +/** + * \brief Activate client certificate authentication. + * + * This is a variant for `br_ssl_server_set_trust_anchor_names()`: the + * trust anchor names are provided not as an array of stand-alone names + * (`br_x500_name` structures), but as an array of trust anchors + * (`br_x509_trust_anchor` structures). The server engine itself will + * only use the `dn` field of each trust anchor. This is meant to allow + * defining a single array of trust anchors, to be used here and in the + * X.509 validation engine itself. + * + * The provided array is linked in, not copied, so that pointer must + * remain valid as long as anchor names may be used. + * + * \param cc server context. + * \param tas trust anchors (only names are used). + * \param num number of trust anchors. + */ +static inline void +br_ssl_server_set_trust_anchor_names_alt(br_ssl_server_context *cc, + const br_x509_trust_anchor *tas, size_t num) +{ + cc->ta_names = NULL; + cc->tas = tas; + cc->num_tas = num; +} + +/** + * \brief Configure the cache for session parameters. + * + * The cache context is provided as a pointer to its first field (vtable + * pointer). + * + * \param cc server context. + * \param vtable session cache context. + */ +static inline void +br_ssl_server_set_cache(br_ssl_server_context *cc, + const br_ssl_session_cache_class **vtable) +{ + cc->cache_vtable = vtable; +} + +/** + * \brief Prepare or reset a server context for handling an incoming client. + * + * \param cc server context. + * \return 1 on success, 0 on error. + */ +int br_ssl_server_reset(br_ssl_server_context *cc); + +/* ===================================================================== */ + +/* + * Context for the simplified I/O context. The transport medium is accessed + * through the low_read() and low_write() callback functions, each with + * its own opaque context pointer. + * + * low_read() read some bytes, at most 'len' bytes, into data[]. The + * returned value is the number of read bytes, or -1 on error. + * The 'len' parameter is guaranteed never to exceed 20000, + * so the length always fits in an 'int' on all platforms. + * + * low_write() write up to 'len' bytes, to be read from data[]. The + * returned value is the number of written bytes, or -1 on + * error. The 'len' parameter is guaranteed never to exceed + * 20000, so the length always fits in an 'int' on all + * parameters. + * + * A socket closure (if the transport medium is a socket) should be reported + * as an error (-1). The callbacks shall endeavour to block until at least + * one byte can be read or written; a callback returning 0 at times is + * acceptable, but this normally leads to the callback being immediately + * called again, so the callback should at least always try to block for + * some time if no I/O can take place. + * + * The SSL engine naturally applies some buffering, so the callbacks need + * not apply buffers of their own. + */ +/** + * \brief Context structure for the simplified SSL I/O wrapper. + * + * This structure is initialised with `br_sslio_init()`. Its contents + * are opaque and shall not be accessed directly. + */ +typedef struct { +#ifndef BR_DOXYGEN_IGNORE + br_ssl_engine_context *engine; + int (*low_read)(void *read_context, + unsigned char *data, size_t len); + void *read_context; + int (*low_write)(void *write_context, + const unsigned char *data, size_t len); + void *write_context; +#endif +} br_sslio_context; + +/** + * \brief Initialise a simplified I/O wrapper context. + * + * The simplified I/O wrapper offers a simpler read/write API for a SSL + * engine (client or server), using the provided callback functions for + * reading data from, or writing data to, the transport medium. + * + * The callback functions have the following semantics: + * + * - Each callback receives an opaque context value (of type `void *`) + * that the callback may use arbitrarily (or possibly ignore). + * + * - `low_read()` reads at least one byte, at most `len` bytes, from + * the transport medium. Read bytes shall be written in `data`. + * + * - `low_write()` writes at least one byte, at most `len` bytes, unto + * the transport medium. The bytes to write are read from `data`. + * + * - The `len` parameter is never zero, and is always lower than 20000. + * + * - The number of processed bytes (read or written) is returned. Since + * that number is less than 20000, it always fits on an `int`. + * + * - On error, the callbacks return -1. Reaching end-of-stream is an + * error. Errors are permanent: the SSL connection is terminated. + * + * - Callbacks SHOULD NOT return 0. This is tolerated, as long as + * callbacks endeavour to block for some non-negligible amount of + * time until at least one byte can be sent or received (if a + * callback returns 0, then the wrapper invokes it again + * immediately). + * + * - Callbacks MAY return as soon as at least one byte is processed; + * they MAY also insist on reading or writing _all_ requested bytes. + * Since SSL is a self-terminated protocol (each record has a length + * header), this does not change semantics. + * + * - Callbacks need not apply any buffering (for performance) since SSL + * itself uses buffers. + * + * \param ctx wrapper context to initialise. + * \param engine SSL engine to wrap. + * \param low_read callback for reading data from the transport. + * \param read_context context pointer for `low_read()`. + * \param low_write callback for writing data on the transport. + * \param write_context context pointer for `low_write()`. + */ +void br_sslio_init(br_sslio_context *ctx, + br_ssl_engine_context *engine, + int (*low_read)(void *read_context, + unsigned char *data, size_t len), + void *read_context, + int (*low_write)(void *write_context, + const unsigned char *data, size_t len), + void *write_context); + +/** + * \brief Read some application data from a SSL connection. + * + * If `len` is zero, then this function returns 0 immediately. In + * all other cases, it never returns 0. + * + * This call returns only when at least one byte has been obtained. + * Returned value is the number of bytes read, or -1 on error. The + * number of bytes always fits on an 'int' (data from a single SSL/TLS + * record is returned). + * + * On error or SSL closure, this function returns -1. The caller should + * inspect the error status on the SSL engine to distinguish between + * normal closure and error. + * + * \param cc SSL wrapper context. + * \param dst destination buffer for application data. + * \param len maximum number of bytes to obtain. + * \return number of bytes obtained, or -1 on error. + */ +int br_sslio_read(br_sslio_context *cc, void *dst, size_t len); + +/** + * \brief Read application data from a SSL connection. + * + * This calls returns only when _all_ requested `len` bytes are read, + * or an error is reached. Returned value is 0 on success, -1 on error. + * A normal (verified) SSL closure before that many bytes are obtained + * is reported as an error by this function. + * + * \param cc SSL wrapper context. + * \param dst destination buffer for application data. + * \param len number of bytes to obtain. + * \return 0 on success, or -1 on error. + */ +int br_sslio_read_all(br_sslio_context *cc, void *dst, size_t len); + +/** + * \brief Write some application data unto a SSL connection. + * + * If `len` is zero, then this function returns 0 immediately. In + * all other cases, it never returns 0. + * + * This call returns only when at least one byte has been written. + * Returned value is the number of bytes written, or -1 on error. The + * number of bytes always fits on an 'int' (less than 20000). + * + * On error or SSL closure, this function returns -1. The caller should + * inspect the error status on the SSL engine to distinguish between + * normal closure and error. + * + * **Important:** SSL is buffered; a "written" byte is a byte that was + * injected into the wrapped SSL engine, but this does not necessarily mean + * that it has been scheduled for sending. Use `br_sslio_flush()` to + * ensure that all pending data has been sent to the transport medium. + * + * \param cc SSL wrapper context. + * \param src source buffer for application data. + * \param len maximum number of bytes to write. + * \return number of bytes written, or -1 on error. + */ +int br_sslio_write(br_sslio_context *cc, const void *src, size_t len); + +/** + * \brief Write application data unto a SSL connection. + * + * This calls returns only when _all_ requested `len` bytes have been + * written, or an error is reached. Returned value is 0 on success, -1 + * on error. A normal (verified) SSL closure before that many bytes are + * written is reported as an error by this function. + * + * **Important:** SSL is buffered; a "written" byte is a byte that was + * injected into the wrapped SSL engine, but this does not necessarily mean + * that it has been scheduled for sending. Use `br_sslio_flush()` to + * ensure that all pending data has been sent to the transport medium. + * + * \param cc SSL wrapper context. + * \param src source buffer for application data. + * \param len number of bytes to write. + * \return 0 on success, or -1 on error. + */ +int br_sslio_write_all(br_sslio_context *cc, const void *src, size_t len); + +/** + * \brief Flush pending data. + * + * This call makes sure that any buffered application data in the + * provided context (including the wrapped SSL engine) has been sent + * to the transport medium (i.e. accepted by the `low_write()` callback + * method). If there is no such pending data, then this function does + * nothing (and returns a success, i.e. 0). + * + * If the underlying transport medium has its own buffers, then it is + * up to the caller to ensure the corresponding flushing. + * + * Returned value is 0 on success, -1 on error. + * + * \param cc SSL wrapper context. + * \return 0 on success, or -1 on error. + */ +int br_sslio_flush(br_sslio_context *cc); + +/** + * \brief Close the SSL connection. + * + * This call runs the SSL closure protocol (sending a `close_notify`, + * receiving the response `close_notify`). When it returns, the SSL + * connection is finished. It is still up to the caller to manage the + * possible transport-level termination, if applicable (alternatively, + * the underlying transport stream may be reused for non-SSL messages). + * + * Returned value is 0 on success, -1 on error. A failure by the peer + * to process the complete closure protocol (i.e. sending back the + * `close_notify`) is an error. + * + * \param cc SSL wrapper context. + * \return 0 on success, or -1 on error. + */ +int br_sslio_close(br_sslio_context *cc); + +/* ===================================================================== */ + +/* + * Symbolic constants for cipher suites. + */ + +/* From RFC 5246 */ +#define BR_TLS_NULL_WITH_NULL_NULL 0x0000 +#define BR_TLS_RSA_WITH_NULL_MD5 0x0001 +#define BR_TLS_RSA_WITH_NULL_SHA 0x0002 +#define BR_TLS_RSA_WITH_NULL_SHA256 0x003B +#define BR_TLS_RSA_WITH_RC4_128_MD5 0x0004 +#define BR_TLS_RSA_WITH_RC4_128_SHA 0x0005 +#define BR_TLS_RSA_WITH_3DES_EDE_CBC_SHA 0x000A +#define BR_TLS_RSA_WITH_AES_128_CBC_SHA 0x002F +#define BR_TLS_RSA_WITH_AES_256_CBC_SHA 0x0035 +#define BR_TLS_RSA_WITH_AES_128_CBC_SHA256 0x003C +#define BR_TLS_RSA_WITH_AES_256_CBC_SHA256 0x003D +#define BR_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA 0x000D +#define BR_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA 0x0010 +#define BR_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA 0x0013 +#define BR_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA 0x0016 +#define BR_TLS_DH_DSS_WITH_AES_128_CBC_SHA 0x0030 +#define BR_TLS_DH_RSA_WITH_AES_128_CBC_SHA 0x0031 +#define BR_TLS_DHE_DSS_WITH_AES_128_CBC_SHA 0x0032 +#define BR_TLS_DHE_RSA_WITH_AES_128_CBC_SHA 0x0033 +#define BR_TLS_DH_DSS_WITH_AES_256_CBC_SHA 0x0036 +#define BR_TLS_DH_RSA_WITH_AES_256_CBC_SHA 0x0037 +#define BR_TLS_DHE_DSS_WITH_AES_256_CBC_SHA 0x0038 +#define BR_TLS_DHE_RSA_WITH_AES_256_CBC_SHA 0x0039 +#define BR_TLS_DH_DSS_WITH_AES_128_CBC_SHA256 0x003E +#define BR_TLS_DH_RSA_WITH_AES_128_CBC_SHA256 0x003F +#define BR_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 0x0040 +#define BR_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 0x0067 +#define BR_TLS_DH_DSS_WITH_AES_256_CBC_SHA256 0x0068 +#define BR_TLS_DH_RSA_WITH_AES_256_CBC_SHA256 0x0069 +#define BR_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 0x006A +#define BR_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 0x006B +#define BR_TLS_DH_anon_WITH_RC4_128_MD5 0x0018 +#define BR_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA 0x001B +#define BR_TLS_DH_anon_WITH_AES_128_CBC_SHA 0x0034 +#define BR_TLS_DH_anon_WITH_AES_256_CBC_SHA 0x003A +#define BR_TLS_DH_anon_WITH_AES_128_CBC_SHA256 0x006C +#define BR_TLS_DH_anon_WITH_AES_256_CBC_SHA256 0x006D + +/* From RFC 4492 */ +#define BR_TLS_ECDH_ECDSA_WITH_NULL_SHA 0xC001 +#define BR_TLS_ECDH_ECDSA_WITH_RC4_128_SHA 0xC002 +#define BR_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA 0xC003 +#define BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA 0xC004 +#define BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA 0xC005 +#define BR_TLS_ECDHE_ECDSA_WITH_NULL_SHA 0xC006 +#define BR_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA 0xC007 +#define BR_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA 0xC008 +#define BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 0xC009 +#define BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 0xC00A +#define BR_TLS_ECDH_RSA_WITH_NULL_SHA 0xC00B +#define BR_TLS_ECDH_RSA_WITH_RC4_128_SHA 0xC00C +#define BR_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA 0xC00D +#define BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA 0xC00E +#define BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA 0xC00F +#define BR_TLS_ECDHE_RSA_WITH_NULL_SHA 0xC010 +#define BR_TLS_ECDHE_RSA_WITH_RC4_128_SHA 0xC011 +#define BR_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA 0xC012 +#define BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 0xC013 +#define BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 0xC014 +#define BR_TLS_ECDH_anon_WITH_NULL_SHA 0xC015 +#define BR_TLS_ECDH_anon_WITH_RC4_128_SHA 0xC016 +#define BR_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA 0xC017 +#define BR_TLS_ECDH_anon_WITH_AES_128_CBC_SHA 0xC018 +#define BR_TLS_ECDH_anon_WITH_AES_256_CBC_SHA 0xC019 + +/* From RFC 5288 */ +#define BR_TLS_RSA_WITH_AES_128_GCM_SHA256 0x009C +#define BR_TLS_RSA_WITH_AES_256_GCM_SHA384 0x009D +#define BR_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 0x009E +#define BR_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 0x009F +#define BR_TLS_DH_RSA_WITH_AES_128_GCM_SHA256 0x00A0 +#define BR_TLS_DH_RSA_WITH_AES_256_GCM_SHA384 0x00A1 +#define BR_TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 0x00A2 +#define BR_TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 0x00A3 +#define BR_TLS_DH_DSS_WITH_AES_128_GCM_SHA256 0x00A4 +#define BR_TLS_DH_DSS_WITH_AES_256_GCM_SHA384 0x00A5 +#define BR_TLS_DH_anon_WITH_AES_128_GCM_SHA256 0x00A6 +#define BR_TLS_DH_anon_WITH_AES_256_GCM_SHA384 0x00A7 + +/* From RFC 5289 */ +#define BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 0xC023 +#define BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 0xC024 +#define BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 0xC025 +#define BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 0xC026 +#define BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 0xC027 +#define BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 0xC028 +#define BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 0xC029 +#define BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 0xC02A +#define BR_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 0xC02B +#define BR_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 0xC02C +#define BR_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 0xC02D +#define BR_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 0xC02E +#define BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 0xC02F +#define BR_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 0xC030 +#define BR_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 0xC031 +#define BR_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 0xC032 + +/* From RFC 6655 and 7251 */ +#define BR_TLS_RSA_WITH_AES_128_CCM 0xC09C +#define BR_TLS_RSA_WITH_AES_256_CCM 0xC09D +#define BR_TLS_RSA_WITH_AES_128_CCM_8 0xC0A0 +#define BR_TLS_RSA_WITH_AES_256_CCM_8 0xC0A1 +#define BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM 0xC0AC +#define BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM 0xC0AD +#define BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 0xC0AE +#define BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 0xC0AF + +/* From RFC 7905 */ +#define BR_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 0xCCA8 +#define BR_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 0xCCA9 +#define BR_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 0xCCAA +#define BR_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 0xCCAB +#define BR_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 0xCCAC +#define BR_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 0xCCAD +#define BR_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 0xCCAE + +/* From RFC 7507 */ +#define BR_TLS_FALLBACK_SCSV 0x5600 + +/* + * Symbolic constants for alerts. + */ +#define BR_ALERT_CLOSE_NOTIFY 0 +#define BR_ALERT_UNEXPECTED_MESSAGE 10 +#define BR_ALERT_BAD_RECORD_MAC 20 +#define BR_ALERT_RECORD_OVERFLOW 22 +#define BR_ALERT_DECOMPRESSION_FAILURE 30 +#define BR_ALERT_HANDSHAKE_FAILURE 40 +#define BR_ALERT_BAD_CERTIFICATE 42 +#define BR_ALERT_UNSUPPORTED_CERTIFICATE 43 +#define BR_ALERT_CERTIFICATE_REVOKED 44 +#define BR_ALERT_CERTIFICATE_EXPIRED 45 +#define BR_ALERT_CERTIFICATE_UNKNOWN 46 +#define BR_ALERT_ILLEGAL_PARAMETER 47 +#define BR_ALERT_UNKNOWN_CA 48 +#define BR_ALERT_ACCESS_DENIED 49 +#define BR_ALERT_DECODE_ERROR 50 +#define BR_ALERT_DECRYPT_ERROR 51 +#define BR_ALERT_PROTOCOL_VERSION 70 +#define BR_ALERT_INSUFFICIENT_SECURITY 71 +#define BR_ALERT_INTERNAL_ERROR 80 +#define BR_ALERT_USER_CANCELED 90 +#define BR_ALERT_NO_RENEGOTIATION 100 +#define BR_ALERT_UNSUPPORTED_EXTENSION 110 +#define BR_ALERT_NO_APPLICATION_PROTOCOL 120 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/enclave/include/bearssl/bearssl_x509.h b/enclave/include/bearssl/bearssl_x509.h new file mode 100644 index 0000000..49d2fba --- /dev/null +++ b/enclave/include/bearssl/bearssl_x509.h @@ -0,0 +1,1397 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef BR_BEARSSL_X509_H__ +#define BR_BEARSSL_X509_H__ + +#include +#include + +#include "bearssl_ec.h" +#include "bearssl_hash.h" +#include "bearssl_rsa.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file bearssl_x509.h + * + * # X.509 Certificate Chain Processing + * + * An X.509 processing engine receives an X.509 chain, chunk by chunk, + * as received from a SSL/TLS client or server (the client receives the + * server's certificate chain, and the server receives the client's + * certificate chain if it requested a client certificate). The chain + * is thus injected in the engine in SSL order (end-entity first). + * + * The engine's job is to return the public key to use for SSL/TLS. + * How exactly that key is obtained and verified is entirely up to the + * engine. + * + * **The "known key" engine** returns a public key which is already known + * from out-of-band information (e.g. the client _remembers_ the key from + * a previous connection, as in the usual SSH model). This is the simplest + * engine since it simply ignores the chain, thereby avoiding the need + * for any decoding logic. + * + * **The "minimal" engine** implements minimal X.509 decoding and chain + * validation: + * + * - The provided chain should validate "as is". There is no attempt + * at reordering, skipping or downloading extra certificates. + * + * - X.509 v1, v2 and v3 certificates are supported. + * + * - Trust anchors are a DN and a public key. Each anchor is either a + * "CA" anchor, or a non-CA. + * + * - If the end-entity certificate matches a non-CA anchor (subject DN + * is equal to the non-CA name, and public key is also identical to + * the anchor key), then this is a _direct trust_ case and the + * remaining certificates are ignored. + * + * - Unless direct trust is applied, the chain must be verifiable up to + * a certificate whose issuer DN matches the DN from a "CA" trust anchor, + * and whose signature is verifiable against that anchor's public key. + * Subsequent certificates in the chain are ignored. + * + * - The engine verifies subject/issuer DN matching, and enforces + * processing of Basic Constraints and Key Usage extensions. The + * Authority Key Identifier, Subject Key Identifier, Issuer Alt Name, + * Subject Directory Attribute, CRL Distribution Points, Freshest CRL, + * Authority Info Access and Subject Info Access extensions are + * ignored. The Subject Alt Name is decoded for the end-entity + * certificate under some conditions (see below). Other extensions + * are ignored if non-critical, or imply chain rejection if critical. + * + * - The Subject Alt Name extension is parsed for names of type `dNSName` + * when decoding the end-entity certificate, and only if there is a + * server name to match. If there is no SAN extension, then the + * Common Name from the subjectDN is used. That name matching is + * case-insensitive and honours a single starting wildcard (i.e. if + * the name in the certificate starts with "`*.`" then this matches + * any word as first element). Note: this name matching is performed + * also in the "direct trust" model. + * + * - DN matching is byte-to-byte equality (a future version might + * include some limited processing for case-insensitive matching and + * whitespace normalisation). + * + * - Successful validation produces a public key type but also a set + * of allowed usages (`BR_KEYTYPE_KEYX` and/or `BR_KEYTYPE_SIGN`). + * The caller is responsible for checking that the key type and + * usages are compatible with the expected values (e.g. with the + * selected cipher suite, when the client validates the server's + * certificate). + * + * **Important caveats:** + * + * - The "minimal" engine does not check revocation status. The relevant + * extensions are ignored, and CRL or OCSP responses are not gathered + * or checked. + * + * - The "minimal" engine does not currently support Name Constraints + * (some basic functionality to handle sub-domains may be added in a + * later version). + * + * - The decoder is not "validating" in the sense that it won't reject + * some certificates with invalid field values when these fields are + * not actually processed. + */ + +/* + * X.509 error codes are in the 32..63 range. + */ + +/** \brief X.509 status: validation was successful; this is not actually + an error. */ +#define BR_ERR_X509_OK 32 + +/** \brief X.509 status: invalid value in an ASN.1 structure. */ +#define BR_ERR_X509_INVALID_VALUE 33 + +/** \brief X.509 status: truncated certificate. */ +#define BR_ERR_X509_TRUNCATED 34 + +/** \brief X.509 status: empty certificate chain (no certificate at all). */ +#define BR_ERR_X509_EMPTY_CHAIN 35 + +/** \brief X.509 status: decoding error: inner element extends beyond + outer element size. */ +#define BR_ERR_X509_INNER_TRUNC 36 + +/** \brief X.509 status: decoding error: unsupported tag class (application + or private). */ +#define BR_ERR_X509_BAD_TAG_CLASS 37 + +/** \brief X.509 status: decoding error: unsupported tag value. */ +#define BR_ERR_X509_BAD_TAG_VALUE 38 + +/** \brief X.509 status: decoding error: indefinite length. */ +#define BR_ERR_X509_INDEFINITE_LENGTH 39 + +/** \brief X.509 status: decoding error: extraneous element. */ +#define BR_ERR_X509_EXTRA_ELEMENT 40 + +/** \brief X.509 status: decoding error: unexpected element. */ +#define BR_ERR_X509_UNEXPECTED 41 + +/** \brief X.509 status: decoding error: expected constructed element, but + is primitive. */ +#define BR_ERR_X509_NOT_CONSTRUCTED 42 + +/** \brief X.509 status: decoding error: expected primitive element, but + is constructed. */ +#define BR_ERR_X509_NOT_PRIMITIVE 43 + +/** \brief X.509 status: decoding error: BIT STRING length is not multiple + of 8. */ +#define BR_ERR_X509_PARTIAL_BYTE 44 + +/** \brief X.509 status: decoding error: BOOLEAN value has invalid length. */ +#define BR_ERR_X509_BAD_BOOLEAN 45 + +/** \brief X.509 status: decoding error: value is off-limits. */ +#define BR_ERR_X509_OVERFLOW 46 + +/** \brief X.509 status: invalid distinguished name. */ +#define BR_ERR_X509_BAD_DN 47 + +/** \brief X.509 status: invalid date/time representation. */ +#define BR_ERR_X509_BAD_TIME 48 + +/** \brief X.509 status: certificate contains unsupported features that + cannot be ignored. */ +#define BR_ERR_X509_UNSUPPORTED 49 + +/** \brief X.509 status: key or signature size exceeds internal limits. */ +#define BR_ERR_X509_LIMIT_EXCEEDED 50 + +/** \brief X.509 status: key type does not match that which was expected. */ +#define BR_ERR_X509_WRONG_KEY_TYPE 51 + +/** \brief X.509 status: signature is invalid. */ +#define BR_ERR_X509_BAD_SIGNATURE 52 + +/** \brief X.509 status: validation time is unknown. */ +#define BR_ERR_X509_TIME_UNKNOWN 53 + +/** \brief X.509 status: certificate is expired or not yet valid. */ +#define BR_ERR_X509_EXPIRED 54 + +/** \brief X.509 status: issuer/subject DN mismatch in the chain. */ +#define BR_ERR_X509_DN_MISMATCH 55 + +/** \brief X.509 status: expected server name was not found in the chain. */ +#define BR_ERR_X509_BAD_SERVER_NAME 56 + +/** \brief X.509 status: unknown critical extension in certificate. */ +#define BR_ERR_X509_CRITICAL_EXTENSION 57 + +/** \brief X.509 status: not a CA, or path length constraint violation */ +#define BR_ERR_X509_NOT_CA 58 + +/** \brief X.509 status: Key Usage extension prohibits intended usage. */ +#define BR_ERR_X509_FORBIDDEN_KEY_USAGE 59 + +/** \brief X.509 status: public key found in certificate is too small. */ +#define BR_ERR_X509_WEAK_PUBLIC_KEY 60 + +/** \brief X.509 status: chain could not be linked to a trust anchor. */ +#define BR_ERR_X509_NOT_TRUSTED 62 + +/** + * \brief Aggregate structure for public keys. + */ +typedef struct { + /** \brief Key type: `BR_KEYTYPE_RSA` or `BR_KEYTYPE_EC` */ + unsigned char key_type; + /** \brief Actual public key. */ + union { + /** \brief RSA public key. */ + br_rsa_public_key rsa; + /** \brief EC public key. */ + br_ec_public_key ec; + } key; +} br_x509_pkey; + +/** + * \brief Distinguished Name (X.500) structure. + * + * The DN is DER-encoded. + */ +typedef struct { + /** \brief Encoded DN data. */ + unsigned char *data; + /** \brief Encoded DN length (in bytes). */ + size_t len; +} br_x500_name; + +/** + * \brief Trust anchor structure. + */ +typedef struct { + /** \brief Encoded DN (X.500 name). */ + br_x500_name dn; + /** \brief Anchor flags (e.g. `BR_X509_TA_CA`). */ + unsigned flags; + /** \brief Anchor public key. */ + br_x509_pkey pkey; +} br_x509_trust_anchor; + +/** + * \brief Trust anchor flag: CA. + * + * A "CA" anchor is deemed fit to verify signatures on certificates. + * A "non-CA" anchor is accepted only for direct trust (server's + * certificate name and key match the anchor). + */ +#define BR_X509_TA_CA 0x0001 + +/* + * Key type: combination of a basic key type (low 4 bits) and some + * optional flags. + * + * For a public key, the basic key type only is set. + * + * For an expected key type, the flags indicate the intended purpose(s) + * for the key; the basic key type may be set to 0 to indicate that any + * key type compatible with the indicated purpose is acceptable. + */ +/** \brief Key type: algorithm is RSA. */ +#define BR_KEYTYPE_RSA 1 +/** \brief Key type: algorithm is EC. */ +#define BR_KEYTYPE_EC 2 + +/** + * \brief Key type: usage is "key exchange". + * + * This value is combined (with bitwise OR) with the algorithm + * (`BR_KEYTYPE_RSA` or `BR_KEYTYPE_EC`) when informing the X.509 + * validation engine that it should find a public key of that type, + * fit for key exchanges (e.g. `TLS_RSA_*` and `TLS_ECDH_*` cipher + * suites). + */ +#define BR_KEYTYPE_KEYX 0x10 + +/** + * \brief Key type: usage is "signature". + * + * This value is combined (with bitwise OR) with the algorithm + * (`BR_KEYTYPE_RSA` or `BR_KEYTYPE_EC`) when informing the X.509 + * validation engine that it should find a public key of that type, + * fit for signatures (e.g. `TLS_ECDHE_*` cipher suites). + */ +#define BR_KEYTYPE_SIGN 0x20 + +/* + * start_chain Called when a new chain is started. If 'server_name' + * is not NULL and non-empty, then it is a name that + * should be looked for in the EE certificate (in the + * SAN extension as dNSName, or in the subjectDN's CN + * if there is no SAN extension). + * The caller ensures that the provided 'server_name' + * pointer remains valid throughout validation. + * + * start_cert Begins a new certificate in the chain. The provided + * length is in bytes; this is the total certificate length. + * + * append Get some additional bytes for the current certificate. + * + * end_cert Ends the current certificate. + * + * end_chain Called at the end of the chain. Returned value is + * 0 on success, or a non-zero error code. + * + * get_pkey Returns the EE certificate public key. + * + * For a complete chain, start_chain() and end_chain() are always + * called. For each certificate, start_cert(), some append() calls, then + * end_cert() are called, in that order. There may be no append() call + * at all if the certificate is empty (which is not valid but may happen + * if the peer sends exactly that). + * + * get_pkey() shall return a pointer to a structure that is valid as + * long as a new chain is not started. This may be a sub-structure + * within the context for the engine. This function MAY return a valid + * pointer to a public key even in some cases of validation failure, + * depending on the validation engine. + */ + +/** + * \brief Class type for an X.509 engine. + * + * A certificate chain validation uses a caller-allocated context, which + * contains the running state for that validation. Methods are called + * in due order: + * + * - `start_chain()` is called at the start of the validation. + * - Certificates are processed one by one, in SSL order (end-entity + * comes first). For each certificate, the following methods are + * called: + * + * - `start_cert()` at the beginning of the certificate. + * - `append()` is called zero, one or more times, to provide + * the certificate (possibly in chunks). + * - `end_cert()` at the end of the certificate. + * + * - `end_chain()` is called when the last certificate in the chain + * was processed. + * - `get_pkey()` is called after chain processing, if the chain + * validation was successful. + * + * A context structure may be reused; the `start_chain()` method shall + * ensure (re)initialisation. + */ +typedef struct br_x509_class_ br_x509_class; +struct br_x509_class_ { + /** + * \brief X.509 context size, in bytes. + */ + size_t context_size; + + /** + * \brief Start a new chain. + * + * This method shall set the vtable (first field) of the context + * structure. + * + * The `server_name`, if not `NULL`, will be considered as a + * fully qualified domain name, to be matched against the `dNSName` + * elements of the end-entity certificate's SAN extension (if there + * is no SAN, then the Common Name from the subjectDN will be used). + * If `server_name` is `NULL` then no such matching is performed. + * + * \param ctx validation context. + * \param server_name server name to match (or `NULL`). + */ + void (*start_chain)(const br_x509_class **ctx, + const char *server_name); + + /** + * \brief Start a new certificate. + * + * \param ctx validation context. + * \param length new certificate length (in bytes). + */ + void (*start_cert)(const br_x509_class **ctx, uint32_t length); + + /** + * \brief Receive some bytes for the current certificate. + * + * This function may be called several times in succession for + * a given certificate. The caller guarantees that for each + * call, `len` is not zero, and the sum of all chunk lengths + * for a certificate matches the total certificate length which + * was provided in the previous `start_cert()` call. + * + * If the new certificate is empty (no byte at all) then this + * function won't be called at all. + * + * \param ctx validation context. + * \param buf certificate data chunk. + * \param len certificate data chunk length (in bytes). + */ + void (*append)(const br_x509_class **ctx, + const unsigned char *buf, size_t len); + + /** + * \brief Finish the current certificate. + * + * This function is called when the end of the current certificate + * is reached. + * + * \param ctx validation context. + */ + void (*end_cert)(const br_x509_class **ctx); + + /** + * \brief Finish the chain. + * + * This function is called at the end of the chain. It shall + * return either 0 if the validation was successful, or a + * non-zero error code. The `BR_ERR_X509_*` constants are + * error codes, though other values may be possible. + * + * \param ctx validation context. + * \return 0 on success, or a non-zero error code. + */ + unsigned (*end_chain)(const br_x509_class **ctx); + + /** + * \brief Get the resulting end-entity public key. + * + * The decoded public key is returned. The returned pointer + * may be valid only as long as the context structure is + * unmodified, i.e. it may cease to be valid if the context + * is released or reused. + * + * This function _may_ return `NULL` if the validation failed. + * However, returning a public key does not mean that the + * validation was wholly successful; some engines may return + * a decoded public key even if the chain did not end on a + * trusted anchor. + * + * If validation succeeded and `usage` is not `NULL`, then + * `*usage` is filled with a combination of `BR_KEYTYPE_SIGN` + * and/or `BR_KEYTYPE_KEYX` that specifies the validated key + * usage types. It is the caller's responsibility to check + * that value against the intended use of the public key. + * + * \param ctx validation context. + * \return the end-entity public key, or `NULL`. + */ + const br_x509_pkey *(*get_pkey)( + const br_x509_class *const *ctx, unsigned *usages); +}; + +/** + * \brief The "known key" X.509 engine structure. + * + * The structure contents are opaque (they shall not be accessed directly), + * except for the first field (the vtable). + * + * The "known key" engine returns an externally configured public key, + * and totally ignores the certificate contents. + */ +typedef struct { + /** \brief Reference to the context vtable. */ + const br_x509_class *vtable; +#ifndef BR_DOXYGEN_IGNORE + br_x509_pkey pkey; + unsigned usages; +#endif +} br_x509_knownkey_context; + +/** + * \brief Class instance for the "known key" X.509 engine. + */ +extern const br_x509_class br_x509_knownkey_vtable; + +/** + * \brief Initialize a "known key" X.509 engine with a known RSA public key. + * + * The `usages` parameter indicates the allowed key usages for that key + * (`BR_KEYTYPE_KEYX` and/or `BR_KEYTYPE_SIGN`). + * + * The provided pointers are linked in, not copied, so they must remain + * valid while the public key may be in usage. + * + * \param ctx context to initialise. + * \param pk known public key. + * \param usages allowed key usages. + */ +void br_x509_knownkey_init_rsa(br_x509_knownkey_context *ctx, + const br_rsa_public_key *pk, unsigned usages); + +/** + * \brief Initialize a "known key" X.509 engine with a known EC public key. + * + * The `usages` parameter indicates the allowed key usages for that key + * (`BR_KEYTYPE_KEYX` and/or `BR_KEYTYPE_SIGN`). + * + * The provided pointers are linked in, not copied, so they must remain + * valid while the public key may be in usage. + * + * \param ctx context to initialise. + * \param pk known public key. + * \param usages allowed key usages. + */ +void br_x509_knownkey_init_ec(br_x509_knownkey_context *ctx, + const br_ec_public_key *pk, unsigned usages); + +#ifndef BR_DOXYGEN_IGNORE +/* + * The minimal X.509 engine has some state buffers which must be large + * enough to simultaneously accommodate: + * -- the public key extracted from the current certificate; + * -- the signature on the current certificate or on the previous + * certificate; + * -- the public key extracted from the EE certificate. + * + * We store public key elements in their raw unsigned big-endian + * encoding. We want to support up to RSA-4096 with a short (up to 64 + * bits) public exponent, thus a buffer for a public key must have + * length at least 520 bytes. Similarly, a RSA-4096 signature has length + * 512 bytes. + * + * Though RSA public exponents can formally be as large as the modulus + * (mathematically, even larger exponents would work, but PKCS#1 forbids + * them), exponents that do not fit on 32 bits are extremely rare, + * notably because some widespread implementations (e.g. Microsoft's + * CryptoAPI) don't support them. Moreover, large public exponent do not + * seem to imply any tangible security benefit, and they increase the + * cost of public key operations. The X.509 "minimal" engine will tolerate + * public exponents of arbitrary size as long as the modulus and the + * exponent can fit together in the dedicated buffer. + * + * EC public keys are shorter than RSA public keys; even with curve + * NIST P-521 (the largest curve we care to support), a public key is + * encoded over 133 bytes only. + */ +#define BR_X509_BUFSIZE_KEY 520 +#define BR_X509_BUFSIZE_SIG 512 +#endif + +/** + * \brief Type for receiving a name element. + * + * An array of such structures can be provided to the X.509 decoding + * engines. If the specified elements are found in the certificate + * subject DN or the SAN extension, then the name contents are copied + * as zero-terminated strings into the buffer. + * + * The decoder converts TeletexString and BMPString to UTF8String, and + * ensures that the resulting string is zero-terminated. If the string + * does not fit in the provided buffer, then the copy is aborted and an + * error is reported. + */ +typedef struct { + /** + * \brief Element OID. + * + * For X.500 name elements (to be extracted from the subject DN), + * this is the encoded OID for the requested name element; the + * first byte shall contain the length of the DER-encoded OID + * value, followed by the OID value (for instance, OID 2.5.4.3, + * for id-at-commonName, will be `03 55 04 03`). This is + * equivalent to full DER encoding with the length but without + * the tag. + * + * For SAN name elements, the first byte (`oid[0]`) has value 0, + * followed by another byte that matches the expected GeneralName + * tag. Allowed second byte values are then: + * + * - 1: `rfc822Name` + * + * - 2: `dNSName` + * + * - 6: `uniformResourceIdentifier` + * + * - 0: `otherName` + * + * If first and second byte are 0, then this is a SAN element of + * type `otherName`; the `oid[]` array should then contain, right + * after the two bytes of value 0, an encoded OID (with the same + * conventions as for X.500 name elements). If a match is found + * for that OID, then the corresponding name element will be + * extracted, as long as it is a supported string type. + */ + const unsigned char *oid; + + /** + * \brief Destination buffer. + */ + char *buf; + + /** + * \brief Length (in bytes) of the destination buffer. + * + * The buffer MUST NOT be smaller than 1 byte. + */ + size_t len; + + /** + * \brief Decoding status. + * + * Status is 0 if the name element was not found, 1 if it was + * found and decoded, or -1 on error. Error conditions include + * an unrecognised encoding, an invalid encoding, or a string + * too large for the destination buffer. + */ + int status; + +} br_name_element; + +/** + * \brief The "minimal" X.509 engine structure. + * + * The structure contents are opaque (they shall not be accessed directly), + * except for the first field (the vtable). + * + * The "minimal" engine performs a rudimentary but serviceable X.509 path + * validation. + */ +typedef struct { + const br_x509_class *vtable; + +#ifndef BR_DOXYGEN_IGNORE + /* Structure for returning the EE public key. */ + br_x509_pkey pkey; + + /* CPU for the T0 virtual machine. */ + struct { + uint32_t *dp; + uint32_t *rp; + const unsigned char *ip; + } cpu; + uint32_t dp_stack[32]; + uint32_t rp_stack[32]; + int err; + + /* Server name to match with the SAN / CN of the EE certificate. */ + const char *server_name; + + /* Validated key usages. */ + unsigned char key_usages; + + /* Explicitly set date and time. */ + uint32_t days, seconds; + + /* Current certificate length (in bytes). Set to 0 when the + certificate has been fully processed. */ + uint32_t cert_length; + + /* Number of certificates processed so far in the current chain. + It is incremented at the end of the processing of a certificate, + so it is 0 for the EE. */ + uint32_t num_certs; + + /* Certificate data chunk. */ + const unsigned char *hbuf; + size_t hlen; + + /* The pad serves as destination for various operations. */ + unsigned char pad[256]; + + /* Buffer for EE public key data. */ + unsigned char ee_pkey_data[BR_X509_BUFSIZE_KEY]; + + /* Buffer for currently decoded public key. */ + unsigned char pkey_data[BR_X509_BUFSIZE_KEY]; + + /* Signature type: signer key type, offset to the hash + function OID (in the T0 data block) and hash function + output length (TBS hash length). */ + unsigned char cert_signer_key_type; + uint16_t cert_sig_hash_oid; + unsigned char cert_sig_hash_len; + + /* Current/last certificate signature. */ + unsigned char cert_sig[BR_X509_BUFSIZE_SIG]; + uint16_t cert_sig_len; + + /* Minimum RSA key length (difference in bytes from 128). */ + int16_t min_rsa_size; + + /* Configured trust anchors. */ + const br_x509_trust_anchor *trust_anchors; + size_t trust_anchors_num; + + /* + * Multi-hasher for the TBS. + */ + unsigned char do_mhash; + br_multihash_context mhash; + unsigned char tbs_hash[64]; + + /* + * Simple hasher for the subject/issuer DN. + */ + unsigned char do_dn_hash; + const br_hash_class *dn_hash_impl; + br_hash_compat_context dn_hash; + unsigned char current_dn_hash[64]; + unsigned char next_dn_hash[64]; + unsigned char saved_dn_hash[64]; + + /* + * Name elements to gather. + */ + br_name_element *name_elts; + size_t num_name_elts; + + /* + * Public key cryptography implementations (signature verification). + */ + br_rsa_pkcs1_vrfy irsa; + br_ecdsa_vrfy iecdsa; + const br_ec_impl *iec; +#endif + +} br_x509_minimal_context; + +/** + * \brief Class instance for the "minimal" X.509 engine. + */ +extern const br_x509_class br_x509_minimal_vtable; + +/** + * \brief Initialise a "minimal" X.509 engine. + * + * The `dn_hash_impl` parameter shall be a hash function internally used + * to match X.500 names (subject/issuer DN, and anchor names). Any standard + * hash function may be used, but a collision-resistant hash function is + * advised. + * + * After initialization, some implementations for signature verification + * (hash functions and signature algorithms) MUST be added. + * + * \param ctx context to initialise. + * \param dn_hash_impl hash function for DN comparisons. + * \param trust_anchors trust anchors. + * \param trust_anchors_num number of trust anchors. + */ +void br_x509_minimal_init(br_x509_minimal_context *ctx, + const br_hash_class *dn_hash_impl, + const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num); + +/** + * \brief Set a supported hash function in an X.509 "minimal" engine. + * + * Hash functions are used with signature verification algorithms. + * Once initialised (with `br_x509_minimal_init()`), the context must + * be configured with the hash functions it shall support for that + * purpose. The hash function identifier MUST be one of the standard + * hash function identifiers (1 to 6, for MD5, SHA-1, SHA-224, SHA-256, + * SHA-384 and SHA-512). + * + * If `impl` is `NULL`, this _removes_ support for the designated + * hash function. + * + * \param ctx validation context. + * \param id hash function identifier (from 1 to 6). + * \param impl hash function implementation (or `NULL`). + */ +static inline void +br_x509_minimal_set_hash(br_x509_minimal_context *ctx, + int id, const br_hash_class *impl) +{ + br_multihash_setimpl(&ctx->mhash, id, impl); +} + +/** + * \brief Set a RSA signature verification implementation in the X.509 + * "minimal" engine. + * + * Once initialised (with `br_x509_minimal_init()`), the context must + * be configured with the signature verification implementations that + * it is supposed to support. If `irsa` is `0`, then the RSA support + * is disabled. + * + * \param ctx validation context. + * \param irsa RSA signature verification implementation (or `0`). + */ +static inline void +br_x509_minimal_set_rsa(br_x509_minimal_context *ctx, + br_rsa_pkcs1_vrfy irsa) +{ + ctx->irsa = irsa; +} + +/** + * \brief Set a ECDSA signature verification implementation in the X.509 + * "minimal" engine. + * + * Once initialised (with `br_x509_minimal_init()`), the context must + * be configured with the signature verification implementations that + * it is supposed to support. + * + * If `iecdsa` is `0`, then this call disables ECDSA support; in that + * case, `iec` may be `NULL`. Otherwise, `iecdsa` MUST point to a function + * that verifies ECDSA signatures with format "asn1", and it will use + * `iec` as underlying elliptic curve support. + * + * \param ctx validation context. + * \param iec elliptic curve implementation (or `NULL`). + * \param iecdsa ECDSA implementation (or `0`). + */ +static inline void +br_x509_minimal_set_ecdsa(br_x509_minimal_context *ctx, + const br_ec_impl *iec, br_ecdsa_vrfy iecdsa) +{ + ctx->iecdsa = iecdsa; + ctx->iec = iec; +} + +/** + * \brief Initialise a "minimal" X.509 engine with default algorithms. + * + * This function performs the same job as `br_x509_minimal_init()`, but + * also sets implementations for RSA, ECDSA, and the standard hash + * functions. + * + * \param ctx context to initialise. + * \param trust_anchors trust anchors. + * \param trust_anchors_num number of trust anchors. + */ +void br_x509_minimal_init_full(br_x509_minimal_context *ctx, + const br_x509_trust_anchor *trust_anchors, size_t trust_anchors_num); + +/** + * \brief Set the validation time for the X.509 "minimal" engine. + * + * The validation time is set as two 32-bit integers, for days and + * seconds since a fixed epoch: + * + * - Days are counted in a proleptic Gregorian calendar since + * January 1st, 0 AD. Year "0 AD" is the one that preceded "1 AD"; + * it is also traditionally known as "1 BC". + * + * - Seconds are counted since midnight, from 0 to 86400 (a count of + * 86400 is possible only if a leap second happened). + * + * The validation date and time is understood in the UTC time zone. + * + * If the validation date and time are not explicitly set, but BearSSL + * was compiled with support for the system clock on the underlying + * platform, then the current time will automatically be used. Otherwise, + * not setting the validation date and time implies a validation + * failure (except in case of direct trust of the EE key). + * + * \param ctx validation context. + * \param days days since January 1st, 0 AD (Gregorian calendar). + * \param seconds seconds since midnight (0 to 86400). + */ +static inline void +br_x509_minimal_set_time(br_x509_minimal_context *ctx, + uint32_t days, uint32_t seconds) +{ + ctx->days = days; + ctx->seconds = seconds; +} + +/** + * \brief Set the minimal acceptable length for RSA keys (X.509 "minimal" + * engine). + * + * The RSA key length is expressed in bytes. The default minimum key + * length is 128 bytes, corresponding to 1017 bits. RSA keys shorter + * than the configured length will be rejected, implying validation + * failure. This setting applies to keys extracted from certificates + * (both end-entity, and intermediate CA) but not to "CA" trust anchors. + * + * \param ctx validation context. + * \param byte_length minimum RSA key length, **in bytes** (not bits). + */ +static inline void +br_x509_minimal_set_minrsa(br_x509_minimal_context *ctx, int byte_length) +{ + ctx->min_rsa_size = (int16_t)(byte_length - 128); +} + +/** + * \brief Set the name elements to gather. + * + * The provided array is linked in the context. The elements are + * gathered from the EE certificate. If the same element type is + * requested several times, then the relevant structures will be filled + * in the order the matching values are encountered in the certificate. + * + * \param ctx validation context. + * \param elts array of name element structures to fill. + * \param num_elts number of name element structures to fill. + */ +static inline void +br_x509_minimal_set_name_elements(br_x509_minimal_context *ctx, + br_name_element *elts, size_t num_elts) +{ + ctx->name_elts = elts; + ctx->num_name_elts = num_elts; +} + +/** + * \brief X.509 decoder context. + * + * This structure is _not_ for X.509 validation, but for extracting + * names and public keys from encoded certificates. Intended usage is + * to use (self-signed) certificates as trust anchors. + * + * Contents are opaque and shall not be accessed directly. + */ +typedef struct { + +#ifndef BR_DOXYGEN_IGNORE + /* Structure for returning the public key. */ + br_x509_pkey pkey; + + /* CPU for the T0 virtual machine. */ + struct { + uint32_t *dp; + uint32_t *rp; + const unsigned char *ip; + } cpu; + uint32_t dp_stack[32]; + uint32_t rp_stack[32]; + int err; + + /* The pad serves as destination for various operations. */ + unsigned char pad[256]; + + /* Flag set when decoding succeeds. */ + unsigned char decoded; + + /* Validity dates. */ + uint32_t notbefore_days, notbefore_seconds; + uint32_t notafter_days, notafter_seconds; + + /* The "CA" flag. This is set to true if the certificate contains + a Basic Constraints extension that asserts CA status. */ + unsigned char isCA; + + /* DN processing: the subject DN is extracted and pushed to the + provided callback. */ + unsigned char copy_dn; + void *append_dn_ctx; + void (*append_dn)(void *ctx, const void *buf, size_t len); + + /* Certificate data chunk. */ + const unsigned char *hbuf; + size_t hlen; + + /* Buffer for decoded public key. */ + unsigned char pkey_data[BR_X509_BUFSIZE_KEY]; + + /* Type of key and hash function used in the certificate signature. */ + unsigned char signer_key_type; + unsigned char signer_hash_id; +#endif + +} br_x509_decoder_context; + +/** + * \brief Initialise an X.509 decoder context for processing a new + * certificate. + * + * The `append_dn()` callback (with opaque context `append_dn_ctx`) + * will be invoked to receive, chunk by chunk, the certificate's + * subject DN. If `append_dn` is `0` then the subject DN will be + * ignored. + * + * \param ctx X.509 decoder context to initialise. + * \param append_dn DN receiver callback (or `0`). + * \param append_dn_ctx context for the DN receiver callback. + */ +void br_x509_decoder_init(br_x509_decoder_context *ctx, + void (*append_dn)(void *ctx, const void *buf, size_t len), + void *append_dn_ctx); + +/** + * \brief Push some certificate bytes into a decoder context. + * + * If `len` is non-zero, then that many bytes are pushed, from address + * `data`, into the provided decoder context. + * + * \param ctx X.509 decoder context. + * \param data certificate data chunk. + * \param len certificate data chunk length (in bytes). + */ +void br_x509_decoder_push(br_x509_decoder_context *ctx, + const void *data, size_t len); + +/** + * \brief Obtain the decoded public key. + * + * Returned value is a pointer to a structure internal to the decoder + * context; releasing or reusing the decoder context invalidates that + * structure. + * + * If decoding was not finished, or failed, then `NULL` is returned. + * + * \param ctx X.509 decoder context. + * \return the public key, or `NULL` on unfinished/error. + */ +static inline br_x509_pkey * +br_x509_decoder_get_pkey(br_x509_decoder_context *ctx) +{ + if (ctx->decoded && ctx->err == 0) { + return &ctx->pkey; + } else { + return NULL; + } +} + +/** + * \brief Get decoder error status. + * + * If no error was reported yet but the certificate decoding is not + * finished, then the error code is `BR_ERR_X509_TRUNCATED`. If decoding + * was successful, then 0 is returned. + * + * \param ctx X.509 decoder context. + * \return 0 on successful decoding, or a non-zero error code. + */ +static inline int +br_x509_decoder_last_error(br_x509_decoder_context *ctx) +{ + if (ctx->err != 0) { + return ctx->err; + } + if (!ctx->decoded) { + return BR_ERR_X509_TRUNCATED; + } + return 0; +} + +/** + * \brief Get the "isCA" flag from an X.509 decoder context. + * + * This flag is set if the decoded certificate claims to be a CA through + * a Basic Constraints extension. This flag should not be read before + * decoding completed successfully. + * + * \param ctx X.509 decoder context. + * \return the "isCA" flag. + */ +static inline int +br_x509_decoder_isCA(br_x509_decoder_context *ctx) +{ + return ctx->isCA; +} + +/** + * \brief Get the issuing CA key type (type of algorithm used to sign the + * decoded certificate). + * + * This is `BR_KEYTYPE_RSA` or `BR_KEYTYPE_EC`. The value 0 is returned + * if the signature type was not recognised. + * + * \param ctx X.509 decoder context. + * \return the issuing CA key type. + */ +static inline int +br_x509_decoder_get_signer_key_type(br_x509_decoder_context *ctx) +{ + return ctx->signer_key_type; +} + +/** + * \brief Get the identifier for the hash function used to sign the decoded + * certificate. + * + * This is 0 if the hash function was not recognised. + * + * \param ctx X.509 decoder context. + * \return the signature hash function identifier. + */ +static inline int +br_x509_decoder_get_signer_hash_id(br_x509_decoder_context *ctx) +{ + return ctx->signer_hash_id; +} + +/** + * \brief Type for an X.509 certificate (DER-encoded). + */ +typedef struct { + /** \brief The DER-encoded certificate data. */ + unsigned char *data; + /** \brief The DER-encoded certificate length (in bytes). */ + size_t data_len; +} br_x509_certificate; + +/** + * \brief Private key decoder context. + * + * The private key decoder recognises RSA and EC private keys, either in + * their raw, DER-encoded format, or wrapped in an unencrypted PKCS#8 + * archive (again DER-encoded). + * + * Structure contents are opaque and shall not be accessed directly. + */ +typedef struct { +#ifndef BR_DOXYGEN_IGNORE + /* Structure for returning the private key. */ + union { + br_rsa_private_key rsa; + br_ec_private_key ec; + } key; + + /* CPU for the T0 virtual machine. */ + struct { + uint32_t *dp; + uint32_t *rp; + const unsigned char *ip; + } cpu; + uint32_t dp_stack[32]; + uint32_t rp_stack[32]; + int err; + + /* Private key data chunk. */ + const unsigned char *hbuf; + size_t hlen; + + /* The pad serves as destination for various operations. */ + unsigned char pad[256]; + + /* Decoded key type; 0 until decoding is complete. */ + unsigned char key_type; + + /* Buffer for the private key elements. It shall be large enough + to accommodate all elements for a RSA-4096 private key (roughly + five 2048-bit integers, possibly a bit more). */ + unsigned char key_data[3 * BR_X509_BUFSIZE_SIG]; +#endif +} br_skey_decoder_context; + +/** + * \brief Initialise a private key decoder context. + * + * \param ctx key decoder context to initialise. + */ +void br_skey_decoder_init(br_skey_decoder_context *ctx); + +/** + * \brief Push some data bytes into a private key decoder context. + * + * If `len` is non-zero, then that many data bytes, starting at address + * `data`, are pushed into the decoder. + * + * \param ctx key decoder context. + * \param data private key data chunk. + * \param len private key data chunk length (in bytes). + */ +void br_skey_decoder_push(br_skey_decoder_context *ctx, + const void *data, size_t len); + +/** + * \brief Get the decoding status for a private key. + * + * Decoding status is 0 on success, or a non-zero error code. If the + * decoding is unfinished when this function is called, then the + * status code `BR_ERR_X509_TRUNCATED` is returned. + * + * \param ctx key decoder context. + * \return 0 on successful decoding, or a non-zero error code. + */ +static inline int +br_skey_decoder_last_error(const br_skey_decoder_context *ctx) +{ + if (ctx->err != 0) { + return ctx->err; + } + if (ctx->key_type == 0) { + return BR_ERR_X509_TRUNCATED; + } + return 0; +} + +/** + * \brief Get the decoded private key type. + * + * Private key type is `BR_KEYTYPE_RSA` or `BR_KEYTYPE_EC`. If decoding is + * not finished or failed, then 0 is returned. + * + * \param ctx key decoder context. + * \return decoded private key type, or 0. + */ +static inline int +br_skey_decoder_key_type(const br_skey_decoder_context *ctx) +{ + if (ctx->err == 0) { + return ctx->key_type; + } else { + return 0; + } +} + +/** + * \brief Get the decoded RSA private key. + * + * This function returns `NULL` if the decoding failed, or is not + * finished, or the key is not RSA. The returned pointer references + * structures within the context that can become invalid if the context + * is reused or released. + * + * \param ctx key decoder context. + * \return decoded RSA private key, or `NULL`. + */ +static inline const br_rsa_private_key * +br_skey_decoder_get_rsa(const br_skey_decoder_context *ctx) +{ + if (ctx->err == 0 && ctx->key_type == BR_KEYTYPE_RSA) { + return &ctx->key.rsa; + } else { + return NULL; + } +} + +/** + * \brief Get the decoded EC private key. + * + * This function returns `NULL` if the decoding failed, or is not + * finished, or the key is not EC. The returned pointer references + * structures within the context that can become invalid if the context + * is reused or released. + * + * \param ctx key decoder context. + * \return decoded EC private key, or `NULL`. + */ +static inline const br_ec_private_key * +br_skey_decoder_get_ec(const br_skey_decoder_context *ctx) +{ + if (ctx->err == 0 && ctx->key_type == BR_KEYTYPE_EC) { + return &ctx->key.ec; + } else { + return NULL; + } +} + +/** + * \brief Encode an RSA private key (raw DER format). + * + * This function encodes the provided key into the "raw" format specified + * in PKCS#1 (RFC 8017, Appendix C, type `RSAPrivateKey`), with DER + * encoding rules. + * + * The key elements are: + * + * - `sk`: the private key (`p`, `q`, `dp`, `dq` and `iq`) + * + * - `pk`: the public key (`n` and `e`) + * + * - `d` (size: `dlen` bytes): the private exponent + * + * The public key elements, and the private exponent `d`, can be + * recomputed from the private key (see `br_rsa_compute_modulus()`, + * `br_rsa_compute_pubexp()` and `br_rsa_compute_privexp()`). + * + * If `dest` is not `NULL`, then the encoded key is written at that + * address, and the encoded length (in bytes) is returned. If `dest` is + * `NULL`, then nothing is written, but the encoded length is still + * computed and returned. + * + * \param dest the destination buffer (or `NULL`). + * \param sk the RSA private key. + * \param pk the RSA public key. + * \param d the RSA private exponent. + * \param dlen the RSA private exponent length (in bytes). + * \return the encoded key length (in bytes). + */ +size_t br_encode_rsa_raw_der(void *dest, const br_rsa_private_key *sk, + const br_rsa_public_key *pk, const void *d, size_t dlen); + +/** + * \brief Encode an RSA private key (PKCS#8 DER format). + * + * This function encodes the provided key into the PKCS#8 format + * (RFC 5958, type `OneAsymmetricKey`). It wraps around the "raw DER" + * format for the RSA key, as implemented by `br_encode_rsa_raw_der()`. + * + * The key elements are: + * + * - `sk`: the private key (`p`, `q`, `dp`, `dq` and `iq`) + * + * - `pk`: the public key (`n` and `e`) + * + * - `d` (size: `dlen` bytes): the private exponent + * + * The public key elements, and the private exponent `d`, can be + * recomputed from the private key (see `br_rsa_compute_modulus()`, + * `br_rsa_compute_pubexp()` and `br_rsa_compute_privexp()`). + * + * If `dest` is not `NULL`, then the encoded key is written at that + * address, and the encoded length (in bytes) is returned. If `dest` is + * `NULL`, then nothing is written, but the encoded length is still + * computed and returned. + * + * \param dest the destination buffer (or `NULL`). + * \param sk the RSA private key. + * \param pk the RSA public key. + * \param d the RSA private exponent. + * \param dlen the RSA private exponent length (in bytes). + * \return the encoded key length (in bytes). + */ +size_t br_encode_rsa_pkcs8_der(void *dest, const br_rsa_private_key *sk, + const br_rsa_public_key *pk, const void *d, size_t dlen); + +/** + * \brief Encode an EC private key (raw DER format). + * + * This function encodes the provided key into the "raw" format specified + * in RFC 5915 (type `ECPrivateKey`), with DER encoding rules. + * + * The private key is provided in `sk`, the public key being `pk`. If + * `pk` is `NULL`, then the encoded key will not include the public key + * in its `publicKey` field (which is nominally optional). + * + * If `dest` is not `NULL`, then the encoded key is written at that + * address, and the encoded length (in bytes) is returned. If `dest` is + * `NULL`, then nothing is written, but the encoded length is still + * computed and returned. + * + * If the key cannot be encoded (e.g. because there is no known OBJECT + * IDENTIFIER for the used curve), then 0 is returned. + * + * \param dest the destination buffer (or `NULL`). + * \param sk the EC private key. + * \param pk the EC public key (or `NULL`). + * \return the encoded key length (in bytes), or 0. + */ +size_t br_encode_ec_raw_der(void *dest, + const br_ec_private_key *sk, const br_ec_public_key *pk); + +/** + * \brief Encode an EC private key (PKCS#8 DER format). + * + * This function encodes the provided key into the PKCS#8 format + * (RFC 5958, type `OneAsymmetricKey`). The curve is identified + * by an OID provided as parameters to the `privateKeyAlgorithm` + * field. The private key value (contents of the `privateKey` field) + * contains the DER encoding of the `ECPrivateKey` type defined in + * RFC 5915, without the `parameters` field (since they would be + * redundant with the information in `privateKeyAlgorithm`). + * + * The private key is provided in `sk`, the public key being `pk`. If + * `pk` is not `NULL`, then the encoded public key is included in the + * `publicKey` field of the private key value (but not in the `publicKey` + * field of the PKCS#8 `OneAsymmetricKey` wrapper). + * + * If `dest` is not `NULL`, then the encoded key is written at that + * address, and the encoded length (in bytes) is returned. If `dest` is + * `NULL`, then nothing is written, but the encoded length is still + * computed and returned. + * + * If the key cannot be encoded (e.g. because there is no known OBJECT + * IDENTIFIER for the used curve), then 0 is returned. + * + * \param dest the destination buffer (or `NULL`). + * \param sk the EC private key. + * \param pk the EC public key (or `NULL`). + * \return the encoded key length (in bytes), or 0. + */ +size_t br_encode_ec_pkcs8_der(void *dest, + const br_ec_private_key *sk, const br_ec_public_key *pk); + +/** + * \brief PEM banner for RSA private key (raw). + */ +#define BR_ENCODE_PEM_RSA_RAW "RSA PRIVATE KEY" + +/** + * \brief PEM banner for EC private key (raw). + */ +#define BR_ENCODE_PEM_EC_RAW "EC PRIVATE KEY" + +/** + * \brief PEM banner for an RSA or EC private key in PKCS#8 format. + */ +#define BR_ENCODE_PEM_PKCS8 "PRIVATE KEY" + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/enclave/include/cmockery.h b/enclave/include/cmockery.h new file mode 100644 index 0000000..665a848 --- /dev/null +++ b/enclave/include/cmockery.h @@ -0,0 +1,487 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CMOCKERY_H_ +#define CMOCKERY_H_ +/* + * These headers or their equivalents should be included prior to including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + */ + +// For those who are used to __func__ from gcc. +#ifndef __func__ +#define __func__ __FUNCTION__ +#endif + +/* Largest integral type. This type should be large enough to hold any + * pointer or integer supported by the compiler. */ +#ifndef LargestIntegralType +#define LargestIntegralType uintmax_t +#endif // LargestIntegralType + +// Printf format used to display LargestIntegralType. +#ifndef LargestIntegralTypePrintfFormat +#ifdef _WIN32 +#define LargestIntegralTypePrintfFormat "%I64x" +#else +#define LargestIntegralTypePrintfFormat "%llx" +#endif // _WIN32 +#endif // LargestIntegralTypePrintfFormat + +// Perform an unsigned cast to LargestIntegralType. +#define cast_to_largest_integral_type(value) \ + ((LargestIntegralType)(value)) + +// Retrieves a return value for the current function. +#define mock() _mock(__func__, __FILE__, __LINE__) + +/* Stores a value to be returned by the specified function later. + * The count parameter returns the number of times the value should be returned + * by mock(). If count is set to -1 the value will always be returned. + */ +#define will_return(function, value) \ + _will_return(#function, __FILE__, __LINE__, \ + cast_to_largest_integral_type(value), 1) +#define will_return_count(function, value, count) \ + _will_return(#function, __FILE__, __LINE__, \ + cast_to_largest_integral_type(value), count) + +/* Add a custom parameter checking function. If the event parameter is NULL + * the event structure is allocated internally by this function. If event + * parameter is provided it must be allocated on the heap and doesn't need to + * be deallocated by the caller. + */ +#define expect_check(function, parameter, check_function, check_data) \ + _expect_check(#function, #parameter, __FILE__, __LINE__, check_function, \ + cast_to_largest_integral_type(check_data), NULL, 0) + +/* Add an event to check a parameter, using check_expected(), against a set of + * values. See will_return() for a description of the count parameter. + */ +#define expect_in_set(function, parameter, value_array) \ + expect_in_set_count(function, parameter, value_array, 1) +#define expect_in_set_count(function, parameter, value_array, count) \ + _expect_in_set(#function, #parameter, __FILE__, __LINE__, value_array, \ + sizeof(value_array) / sizeof((value_array)[0]), count) +#define expect_not_in_set(function, parameter, value_array) \ + expect_not_in_set_count(function, parameter, value_array, 1) +#define expect_not_in_set_count(function, parameter, value_array, count) \ + _expect_not_in_set( \ + #function, #parameter, __FILE__, __LINE__, value_array, \ + sizeof(value_array) / sizeof((value_array)[0]), count) + + +/* Add an event to check a parameter, using check_expected(), against a + * signed range. Where range is minimum <= value <= maximum. + * See will_return() for a description of the count parameter. + */ +#define expect_in_range(function, parameter, minimum, maximum) \ + expect_in_range_count(function, parameter, minimum, maximum, 1) +#define expect_in_range_count(function, parameter, minimum, maximum, count) \ + _expect_in_range(#function, #parameter, __FILE__, __LINE__, minimum, \ + maximum, count) + +/* Add an event to check a parameter, using check_expected(), against a + * signed range. Where range is value < minimum or value > maximum. + * See will_return() for a description of the count parameter. + */ +#define expect_not_in_range(function, parameter, minimum, maximum) \ + expect_not_in_range_count(function, parameter, minimum, maximum, 1) +#define expect_not_in_range_count(function, parameter, minimum, maximum, \ + count) \ + _expect_not_in_range(#function, #parameter, __FILE__, __LINE__, \ + minimum, maximum, count) + +/* Add an event to check whether a parameter, using check_expected(), is or + * isn't a value. See will_return() for a description of the count parameter. + */ +#define expect_value(function, parameter, value) \ + expect_value_count(function, parameter, value, 1) +#define expect_value_count(function, parameter, value, count) \ + _expect_value(#function, #parameter, __FILE__, __LINE__, \ + cast_to_largest_integral_type(value), count) +#define expect_not_value(function, parameter, value) \ + expect_not_value_count(function, parameter, value, 1) +#define expect_not_value_count(function, parameter, value, count) \ + _expect_not_value(#function, #parameter, __FILE__, __LINE__, \ + cast_to_largest_integral_type(value), count) + +/* Add an event to check whether a parameter, using check_expected(), + * is or isn't a string. See will_return() for a description of the count + * parameter. + */ +#define expect_string(function, parameter, string) \ + expect_string_count(function, parameter, string, 1) +#define expect_string_count(function, parameter, string, count) \ + _expect_string(#function, #parameter, __FILE__, __LINE__, \ + (const char*)(string), count) +#define expect_not_string(function, parameter, string) \ + expect_not_string_count(function, parameter, string, 1) +#define expect_not_string_count(function, parameter, string, count) \ + _expect_not_string(#function, #parameter, __FILE__, __LINE__, \ + (const char*)(string), count) + +/* Add an event to check whether a parameter, using check_expected() does or + * doesn't match an area of memory. See will_return() for a description of + * the count parameter. + */ +#define expect_memory(function, parameter, memory, size) \ + expect_memory_count(function, parameter, memory, size, 1) +#define expect_memory_count(function, parameter, memory, size, count) \ + _expect_memory(#function, #parameter, __FILE__, __LINE__, \ + (const void*)(memory), size, count) +#define expect_not_memory(function, parameter, memory, size) \ + expect_not_memory_count(function, parameter, memory, size, 1) +#define expect_not_memory_count(function, parameter, memory, size, count) \ + _expect_not_memory(#function, #parameter, __FILE__, __LINE__, \ + (const void*)(memory), size, count) + + +/* Add an event to allow any value for a parameter checked using + * check_expected(). See will_return() for a description of the count + * parameter. + */ +#define expect_any(function, parameter) \ + expect_any_count(function, parameter, 1) +#define expect_any_count(function, parameter, count) \ + _expect_any(#function, #parameter, __FILE__, __LINE__, count) + +/* Determine whether a function parameter is correct. This ensures the next + * value queued by one of the expect_*() macros matches the specified variable. + */ +#define check_expected(parameter) \ + _check_expected(__func__, #parameter, __FILE__, __LINE__, \ + cast_to_largest_integral_type(parameter)) + +// Assert that the given expression is true. +#define assert_true(c) _assert_true(cast_to_largest_integral_type(c), #c, \ + __FILE__, __LINE__) +// Assert that the given expression is false. +#define assert_false(c) _assert_true(!(cast_to_largest_integral_type(c)), #c, \ + __FILE__, __LINE__) + +// Assert that the two given integers are equal, otherwise fail. +#define assert_int_equal(a, b) \ + _assert_int_equal(cast_to_largest_integral_type(a), \ + cast_to_largest_integral_type(b), \ + __FILE__, __LINE__) +// Assert that the two given integers are not equal, otherwise fail. +#define assert_int_not_equal(a, b) \ + _assert_int_not_equal(cast_to_largest_integral_type(a), \ + cast_to_largest_integral_type(b), \ + __FILE__, __LINE__) + +// Assert that the two given strings are equal, otherwise fail. +#define assert_string_equal(a, b) \ + _assert_string_equal((const char*)(a), (const char*)(b), __FILE__, \ + __LINE__) +// Assert that the two given strings are not equal, otherwise fail. +#define assert_string_not_equal(a, b) \ + _assert_string_not_equal((const char*)(a), (const char*)(b), __FILE__, \ + __LINE__) + +// Assert that the two given areas of memory are equal, otherwise fail. +#define assert_memory_equal(a, b, size) \ + _assert_memory_equal((const char*)(a), (const char*)(b), size, __FILE__, \ + __LINE__) +// Assert that the two given areas of memory are not equal, otherwise fail. +#define assert_memory_not_equal(a, b, size) \ + _assert_memory_not_equal((const char*)(a), (const char*)(b), size, \ + __FILE__, __LINE__) + +// Assert that the specified value is >= minimum and <= maximum. +#define assert_in_range(value, minimum, maximum) \ + _assert_in_range( \ + cast_to_largest_integral_type(value), \ + cast_to_largest_integral_type(minimum), \ + cast_to_largest_integral_type(maximum), __FILE__, __LINE__) + +// Assert that the specified value is < minumum or > maximum +#define assert_not_in_range(value, minimum, maximum) \ + _assert_not_in_range( \ + cast_to_largest_integral_type(value), \ + cast_to_largest_integral_type(minimum), \ + cast_to_largest_integral_type(maximum), __FILE__, __LINE__) + +// Assert that the specified value is within a set. +#define assert_in_set(value, values, number_of_values) \ + _assert_in_set(value, values, number_of_values, __FILE__, __LINE__) +// Assert that the specified value is not within a set. +#define assert_not_in_set(value, values, number_of_values) \ + _assert_not_in_set(value, values, number_of_values, __FILE__, __LINE__) + + +// Forces the test to fail immediately and quit. +#define fail() _fail(__FILE__, __LINE__) + +// Generic method to kick off testing +#define run_test(f) _run_test(#f, f, NULL, UNIT_TEST_FUNCTION_TYPE_TEST, NULL) + +// Initializes a UnitTest structure. +#define unit_test(f) { #f, f, UNIT_TEST_FUNCTION_TYPE_TEST } +#define unit_test_setup(test, setup) \ + { #test "_" #setup, setup, UNIT_TEST_FUNCTION_TYPE_SETUP } +#define unit_test_teardown(test, teardown) \ + { #test "_" #teardown, teardown, UNIT_TEST_FUNCTION_TYPE_TEARDOWN } + +/* Initialize an array of UnitTest structures with a setup function for a test + * and a teardown function. Either setup or teardown can be NULL. + */ +#define unit_test_setup_teardown(test, setup, teardown) \ + unit_test_setup(test, setup), \ + unit_test(test), \ + unit_test_teardown(test, teardown) + +/* + * Run tests specified by an array of UnitTest structures. The following + * example illustrates this macro's use with the unit_test macro. + * + * void Test0(); + * void Test1(); + * + * int main(int argc, char* argv[]) { + * const UnitTest tests[] = { + * unit_test(Test0); + * unit_test(Test1); + * }; + * return run_tests(tests); + * } + */ +#define run_tests(tests) _run_tests(tests, sizeof(tests) / sizeof(tests)[0]) + +// Dynamic allocators +#define test_malloc(size) _test_malloc(size, __FILE__, __LINE__) +#define test_memalign(alignment, size) _test_memalign(alignment, size, __FILE__, __LINE__) +#define test_calloc(num, size) _test_calloc(num, size, __FILE__, __LINE__) +#define test_free(ptr) _test_free(ptr, __FILE__, __LINE__) + +// Redirect malloc, calloc and free to the unit test allocators. +#if UNIT_TESTING +#define malloc test_malloc +#define memalign test_memalign +#define calloc test_calloc +#define free test_free +#endif // UNIT_TESTING + +/* + * Ensure mock_assert() is called. If mock_assert() is called the assert + * expression string is returned. + * For example: + * + * #define assert mock_assert + * + * void showmessage(const char *message) { + * assert(message); + * } + * + * int main(int argc, const char* argv[]) { + * expect_assert_failure(show_message(NULL)); + * printf("succeeded\n"); + * return 0; + * } + */ +#define expect_assert_failure(function_call) \ + { \ + const int expression = setjmp(global_expect_assert_env); \ + global_expecting_assert = 1; \ + if (expression) { \ + print_message("Expected assertion %s occurred\n", \ + *((const char**)&expression)); \ + global_expecting_assert = 0; \ + } else { \ + function_call ; \ + global_expecting_assert = 0; \ + print_error("Expected assert in %s\n", #function_call); \ + _fail(__FILE__, __LINE__); \ + } \ + } + +// Function prototype for setup, test and teardown functions. +typedef void (*UnitTestFunction)(void **state); + +// Function that determines whether a function parameter value is correct. +typedef int (*CheckParameterValue)(const LargestIntegralType value, + const LargestIntegralType check_value_data); + +// Type of the unit test function. +typedef enum UnitTestFunctionType { + UNIT_TEST_FUNCTION_TYPE_TEST = 0, + UNIT_TEST_FUNCTION_TYPE_SETUP, + UNIT_TEST_FUNCTION_TYPE_TEARDOWN, +} UnitTestFunctionType; + +/* Stores a unit test function with its name and type. + * NOTE: Every setup function must be paired with a teardown function. It's + * possible to specify NULL function pointers. + */ +typedef struct UnitTest { + const char* name; + UnitTestFunction function; + UnitTestFunctionType function_type; +} UnitTest; + + +// Location within some source code. +typedef struct SourceLocation { + const char* file; + int line; +} SourceLocation; + +// Event that's called to check a parameter value. +typedef struct CheckParameterEvent { + SourceLocation location; + const char *parameter_name; + CheckParameterValue check_value; + LargestIntegralType check_value_data; +} CheckParameterEvent; + +// Used by expect_assert_failure() and mock_assert(). +extern int global_expecting_assert; +extern jmp_buf global_expect_assert_env; + +// Retrieves a value for the given function, as set by "will_return". +LargestIntegralType _mock(const char * const function, const char* const file, + const int line); + +void _expect_check( + const char* const function, const char* const parameter, + const char* const file, const int line, + const CheckParameterValue check_function, + const LargestIntegralType check_data, CheckParameterEvent * const event, + const int count); + +void _expect_in_set( + const char* const function, const char* const parameter, + const char* const file, const int line, const LargestIntegralType values[], + const size_t number_of_values, const int count); +void _expect_not_in_set( + const char* const function, const char* const parameter, + const char* const file, const int line, const LargestIntegralType values[], + const size_t number_of_values, const int count); + +void _expect_in_range( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType minimum, + const LargestIntegralType maximum, const int count); +void _expect_not_in_range( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType minimum, + const LargestIntegralType maximum, const int count); + +void _expect_value( + const char* const function, const char* const parameter, + const char* const file, const int line, const LargestIntegralType value, + const int count); +void _expect_not_value( + const char* const function, const char* const parameter, + const char* const file, const int line, const LargestIntegralType value, + const int count); + +void _expect_string( + const char* const function, const char* const parameter, + const char* const file, const int line, const char* string, + const int count); +void _expect_not_string( + const char* const function, const char* const parameter, + const char* const file, const int line, const char* string, + const int count); + +void _expect_memory( + const char* const function, const char* const parameter, + const char* const file, const int line, const void* const memory, + const size_t size, const int count); +void _expect_not_memory( + const char* const function, const char* const parameter, + const char* const file, const int line, const void* const memory, + const size_t size, const int count); + +void _expect_any( + const char* const function, const char* const parameter, + const char* const file, const int line, const int count); + +void _check_expected( + const char * const function_name, const char * const parameter_name, + const char* file, const int line, const LargestIntegralType value); + +// Can be used to replace assert in tested code so that in conjuction with +// check_assert() it's possible to determine whether an assert condition has +// failed without stopping a test. +void mock_assert(const int result, const char* const expression, + const char * const file, const int line); + +void _will_return(const char * const function_name, const char * const file, + const int line, const LargestIntegralType value, + const int count); +void _assert_true(const LargestIntegralType result, + const char* const expression, + const char * const file, const int line); +void _assert_int_equal( + const LargestIntegralType a, const LargestIntegralType b, + const char * const file, const int line); +void _assert_int_not_equal( + const LargestIntegralType a, const LargestIntegralType b, + const char * const file, const int line); +void _assert_string_equal(const char * const a, const char * const b, + const char * const file, const int line); +void _assert_string_not_equal(const char * const a, const char * const b, + const char *file, const int line); +void _assert_memory_equal(const void * const a, const void * const b, + const size_t size, const char* const file, + const int line); +void _assert_memory_not_equal(const void * const a, const void * const b, + const size_t size, const char* const file, + const int line); +void _assert_in_range( + const LargestIntegralType value, const LargestIntegralType minimum, + const LargestIntegralType maximum, const char* const file, const int line); +void _assert_not_in_range( + const LargestIntegralType value, const LargestIntegralType minimum, + const LargestIntegralType maximum, const char* const file, const int line); +void _assert_in_set( + const LargestIntegralType value, const LargestIntegralType values[], + const size_t number_of_values, const char* const file, const int line); +void _assert_not_in_set( + const LargestIntegralType value, const LargestIntegralType values[], + const size_t number_of_values, const char* const file, const int line); + +void* _test_malloc(const size_t size, const char* file, const int line); +void* _test_memalign(const size_t alignment, const size_t size, const char* file, const int line); +void* _test_calloc(const size_t number_of_elements, const size_t size, + const char* file, const int line); +void _test_free(void* const ptr, const char* file, const int line); + +void _fail(const char * const file, const int line); +int _run_test( + const char * const function_name, const UnitTestFunction Function, + void ** const state, const UnitTestFunctionType function_type, + const void* const heap_check_point); +int _run_tests(const UnitTest * const tests, const size_t number_of_tests); + +// Standard output and error print methods. +void print_message(const char* const format, ...); +void print_error(const char* const format, ...); +void vprint_message(const char* const format, va_list args); +void vprint_error(const char* const format, va_list args); + +#endif // CMOCKERY_H_ diff --git a/enclave/include/curve25519-donna.h b/enclave/include/curve25519-donna.h new file mode 100644 index 0000000..cac6218 --- /dev/null +++ b/enclave/include/curve25519-donna.h @@ -0,0 +1,7 @@ +#ifndef _CURVE25519_DONNA_H +#define _CURVE25519_DONNA_H + +int curve25519_donna(unsigned char *, const unsigned char *, const unsigned char *); + +#endif + diff --git a/enclave/include/dlmalloc.h b/enclave/include/dlmalloc.h new file mode 100644 index 0000000..a5f78b3 --- /dev/null +++ b/enclave/include/dlmalloc.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#ifndef _DLMALLOC_H +#define _DLMALLOC_H + +#include + +struct mallinfo dlmallinfo(); +struct mallinfo { + int arena; /* non-mmapped space allocated from system */ + int ordblks; /* number of free chunks */ + int smblks; /* always 0 */ + int hblks; /* always 0 */ + int hblkhd; /* space in mmapped regions */ + int usmblks; /* maximum total allocated space */ + int fsmblks; /* always 0 */ + int uordblks; /* total allocated space */ + int fordblks; /* total free space */ + int keepcost; /* releasable (via malloc_trim) space */ +}; + +#endif diff --git a/enclave/include/kbupd.proto b/enclave/include/kbupd.proto new file mode 100644 index 0000000..7d4be1f --- /dev/null +++ b/enclave/include/kbupd.proto @@ -0,0 +1,473 @@ +syntax = "proto2"; + +package protobufs.kbupd; + +import "kbupd_client.proto"; + +// +// shared types +// + +message ServiceId { + required bytes id = 1; +} + +message BackupId { + required bytes id = 1; +} + +message PartitionKeyRangePB { + required BackupId first = 1; + required BackupId last = 2; +} + +message IasReport { + required bytes body = 2; + required bytes signature = 3; + repeated bytes certificates = 4; +} + +message AttestationParameters { + required uint64 unix_timestamp_seconds = 1; +} + +enum XferControlCommand { + START = 1; + FINISH = 2; + CANCEL = 3; + PAUSE = 4; + RESUME = 5; +} + +// +// transaction requests +// + +message CreateBackupRequest { + required BackupId backup_id = 1; +} + +message CreateBackupReply { + required bytes nonce = 2; + optional uint32 tries = 3; +} + +message DeleteBackupRequest { + required BackupId backup_id = 1; +} + +message DeleteBackupReply { +} + +// +// untrusted messages +// + +message UntrustedMessageBatch { + repeated UntrustedMessage messages = 1; +} + +message UntrustedMessage { + oneof inner { + StartFrontendRequest start_frontend_request = 1; + StartReplicaRequest start_replica_request = 2; + StartReplicaGroupRequest start_replica_group_request = 3; + UntrustedTransactionRequest untrusted_transaction_request = 4; + UntrustedXferRequest untrusted_xfer_request = 5; + GetEnclaveStatusRequest get_enclave_status_request = 6; + + GetQeInfoReply get_qe_info_reply = 8; + GetQuoteReply get_quote_reply = 9; + GetAttestationReply get_attestation_reply = 10; + + NewMessageSignal new_message_signal = 11; + TimerTickSignal timer_tick_signal = 12; + SetFrontendConfigSignal set_frontend_config_signal = 13; + SetReplicaConfigSignal set_replica_config_signal = 14; + ResetPeerSignal reset_peer_signal = 15; + SetVerboseLoggingSignal set_verbose_logging_signal = 16; + } + reserved 7; +} + +message PartitionConfig { + required bytes group_id = 1; + optional PartitionKeyRangePB range = 2; + repeated bytes node_ids = 3; +} + +message StartFrontendRequest { + repeated PartitionConfig partitions = 1; + required EnclaveFrontendConfig config = 2; +} + +message EnclaveFrontendConfig { + required uint32 replica_timeout_ticks = 1; + required uint32 request_quote_ticks = 2; + required uint32 min_connect_timeout_ticks = 3; + required uint32 max_connect_timeout_ticks = 4; + required uint32 pending_request_count = 5; + required uint32 pending_request_ttl = 6; +} + +message SourcePartitionConfig { + required PartitionKeyRangePB range = 1; + repeated bytes node_ids = 2; +} + +message StartReplicaRequest { + required EnclaveReplicaConfig config = 1; +} + +message EnclaveReplicaConfig { + required uint32 election_timeout_ticks = 1; + required uint32 heartbeat_timeout_ticks = 2; + required uint32 request_quote_ticks = 3; + required uint32 min_connect_timeout_ticks = 4; + required uint32 max_connect_timeout_ticks = 5; + required uint32 attestation_expiry_ticks = 6; + required uint32 replication_chunk_size = 7; + required uint32 transfer_chunk_size = 8; + required uint32 storage_page_cache_size = 10; + required uint32 raft_log_index_page_cache_size = 13; + required uint32 max_frontend_count = 14; +} + +message StartReplicaGroupRequest { + repeated bytes peer_node_ids = 1; + required EnclaveReplicaGroupConfig config = 2; + optional SourcePartitionConfig source_partition = 3; +} + +message EnclaveReplicaGroupConfig { + required uint32 storage_size = 1; + required uint64 raft_log_data_size = 2; + required uint32 raft_log_index_size = 3; +} + +message NewMessageSignal { + required bytes node_id = 1; + required bytes data = 2; + required bool syn = 3; +} + +message TimerTickSignal { + required fixed64 now_secs = 1; +} + +message SetFrontendConfigSignal { + required EnclaveFrontendConfig config = 1; +} + +message SetReplicaConfigSignal { + required EnclaveReplicaConfig config = 1; +} + +message ResetPeerSignal { + required bytes peer_node_id = 1; +} + +message SetVerboseLoggingSignal { + required bool verbose_logging = 1; +} + +message GetQeInfoReply { + required bytes mrenclave = 1; + required uint64 flags = 2; + required uint64 xfrm = 3; + required uint32 misc_select = 4; + required uint32 config_svn = 5; + required bytes config_id = 6; +} + +message GetQuoteReply { + required bytes request_id = 1; + required bytes sgx_quote = 2; +} + +message GetAttestationReply { + required bytes request_id = 1; + required IasReport ias_report = 2; +} + +message UntrustedTransactionRequest { + required uint64 request_id = 1; + oneof data { + CreateBackupRequest create_backup_request = 2; + DeleteBackupRequest delete_backup_request = 3; + }; +} + +message UntrustedXferRequest { + required uint64 request_id = 1; + oneof data { + XferControlCommand xfer_control_command = 2; + }; +} + +message GetEnclaveStatusRequest { + required bool memory_status = 1; +} + +// +// enclave messages +// + +message EnclaveMessageBatch { + repeated EnclaveMessage messages = 1; +} + +message EnclaveMessage { + oneof inner { + StartFrontendReply start_frontend_reply = 1; + StartReplicaReply start_replica_reply = 2; + StartReplicaGroupReply start_replica_group_reply = 3; + UntrustedTransactionReply untrusted_transaction_reply = 4; + UntrustedXferReply untrusted_xfer_reply = 5; + GetEnclaveStatusReply get_enclave_status_reply = 6; + + SendMessageRequest send_message_request = 7; + GetQeInfoRequest get_qe_info_request = 8; + GetQuoteRequest get_quote_request = 9; + GetAttestationRequest get_attestation_request = 10; + + EnclaveLogSignal enclave_log_signal = 11; + EnclaveTransactionSignal enclave_transaction_signal = 12; + } +} + +message StartFrontendReply { + required bytes node_id = 1; +} + +message StartReplicaReply { + required bytes node_id = 1; +} + +message StartReplicaGroupReply { + optional ServiceId service_id = 1; + optional bytes group_id = 2; +} + +message GetEnclaveStatusReply { + oneof inner { + EnclaveReplicaStatus replica_status = 1; + EnclaveFrontendStatus frontend_status = 2; + } +} + +message SendMessageRequest { + required bytes node_id = 1; + required bytes data = 2; + required bool syn = 3; + optional bytes debug_msg = 4; +} + +message GetQeInfoRequest { +} + +message GetQuoteRequest { + required bytes request_id = 1; + required bytes sgx_report = 2; +} + +message GetAttestationRequest { + required bytes request_id = 1; + required bytes sgx_quote = 2; +} + +message UntrustedTransactionReply { + required uint64 request_id = 1; + oneof data { + CreateBackupReply create_backup_reply = 2; + DeleteBackupReply delete_backup_reply = 3; + }; +} + +enum UntrustedXferReplyStatus { + UNKNOWN = 0; + OK = 1; + NOT_LEADER = 2; + INVALID_STATE = 3; +} + +message UntrustedXferReply { + required uint64 request_id = 1; + required UntrustedXferReplyStatus status = 2; +} + +enum EnclaveLogLevel { + ERROR = 0; + WARN = 1; + INFO = 2; + DEBUG = 3; +} + +message EnclaveLogSignal { + required bytes message = 1; + required bytes module = 2; + required bytes file = 3; + required uint32 line = 4; + required EnclaveLogLevel level = 5; +} + +message EnclaveTransactionSignal { + required uint64 log_index = 1; + oneof transaction { + EnclaveFrontendRequestTransaction frontend_request = 2; + EnclaveStartXferTransaction start_xfer = 3; + EnclaveSetSidTransaction set_sid = 4; + EnclaveRemoveChunkTransaction remove_chunk = 5; + EnclaveApplyChunkTransaction apply_chunk = 6; + EnclavePauseXferTransaction pause_xfer = 7; + EnclaveResumeXferTransaction resume_xfer = 8; + EnclaveFinishXferTransaction finish_xfer = 9; + EnclaveSetTimeTransaction set_time = 10; + }; +} + +// +// enclave transactions +// + +message EnclaveFrontendRequestTransaction { + oneof transaction { + EnclaveCreateBackupTransaction create = 1; + EnclaveBackupTransaction backup = 2; + EnclaveRestoreTransaction restore = 3; + EnclaveDeleteBackupTransaction delete = 4; + + EnclaveTransactionErrorXferInProgress xfer_in_progress = 5; + EnclaveTransactionErrorWrongPartition wrong_partition = 6; + EnclaveTransactionErrorInvalidRequest invalid_request = 7; + EnclaveTransactionErrorInternalError internal_error = 8; + } +} +message EnclaveCreateBackupTransaction { + required BackupId backup_id = 1; +} +message EnclaveBackupTransaction { + required BackupId backup_id = 1; + required kbupd_client.BackupResponse.Status status = 2; +} +message EnclaveRestoreTransaction { + required BackupId backup_id = 1; + required kbupd_client.RestoreResponse.Status status = 2; +} +message EnclaveDeleteBackupTransaction { + required BackupId backup_id = 1; +} +message EnclaveTransactionErrorXferInProgress { +} +message EnclaveTransactionErrorWrongPartition { + required bool new_partition_unknown = 1; +} +message EnclaveTransactionErrorInvalidRequest { +} +message EnclaveTransactionErrorInternalError { +} +message EnclaveStartXferTransaction { +} +message EnclaveSetSidTransaction { + optional ServiceId service_id = 1; +} +message EnclaveRemoveChunkTransaction { + optional PartitionKeyRangePB chunk_range = 1; +} +message EnclaveApplyChunkTransaction { + optional PartitionKeyRangePB chunk_range = 1; + repeated BackupId chunk_ids = 2; +} +message EnclavePauseXferTransaction { +} +message EnclaveResumeXferTransaction { + optional PartitionKeyRangePB chunk_range = 1; +} +message EnclaveFinishXferTransaction { +} +message EnclaveSetTimeTransaction { + optional uint64 now_secs = 1; +} + +// +// enclave status +// + +message EnclaveMemoryStatus { + required uint32 footprint_bytes = 1; + required uint32 used_bytes = 2; + required uint32 free_chunks = 3; +} + +message EnclaveReplicaStatus { + optional EnclaveMemoryStatus memory_status = 1; + optional EnclaveReplicaPartitionStatus partition = 2; +} + +message EnclaveReplicaPartitionStatus { + required bytes group_id = 1; + optional bytes service_id = 2; + optional PartitionKeyRangePB range = 3; + repeated EnclavePeerStatus peers = 4; + required AttestationParameters min_attestation = 5; + required bool is_leader = 6; + required uint64 current_term = 7; + required uint64 prev_log_index = 8; + required uint64 last_applied_index = 9; + required uint64 commit_index = 10; + required uint64 last_log_index = 11; + required uint64 last_log_term = 12; + required uint64 log_data_length = 13; + required uint64 backup_count = 14; + + oneof xfer_status { + EnclaveIncomingXferStatus incoming_xfer_status = 15; + EnclaveOutgoingXferStatus outgoing_xfer_status = 16; + } +} + +message EnclavePeerStatus { + required bytes node_id = 1; + optional AttestationParameters attestation = 2; + optional EnclavePeerReplicationStatus replication_status = 3; + required bool is_leader = 4; + required uint64 inflight_requests = 5; + required uint64 unsent_requests = 6; +} + +message EnclavePeerReplicationStatus { + required uint64 next_index = 1; + required uint64 match_index = 2; + optional uint64 inflight_index = 3; + required bool probing = 4; +} + +message EnclaveIncomingXferStatus { + required PartitionKeyRangePB desired_range = 1; + repeated EnclavePeerStatus nodes = 2; +} + +message EnclaveOutgoingXferStatus { + required bytes group_id = 1; + required PartitionKeyRangePB full_xfer_range = 2; + optional PartitionKeyRangePB current_chunk_range = 3; + required bool paused = 4; + optional AttestationParameters min_attestation = 5; + repeated EnclavePeerStatus nodes = 6; +} + +message EnclaveFrontendStatus { + optional EnclaveMemoryStatus memory_status = 1; + repeated EnclaveFrontendPartitionStatus partitions = 2; + repeated EnclaveFrontendRangeStatus ranges = 3; +} + +message EnclaveFrontendPartitionStatus { + required bytes group_id = 1; + repeated EnclavePeerStatus nodes = 2; +} + +message EnclaveFrontendRangeStatus { + required PartitionKeyRangePB range = 1; + required bytes group_id = 2; +} diff --git a/enclave/include/kbupd_client.proto b/enclave/include/kbupd_client.proto new file mode 100644 index 0000000..4a93180 --- /dev/null +++ b/enclave/include/kbupd_client.proto @@ -0,0 +1,79 @@ +syntax = "proto2"; + +package protobufs.kbupd_client; + +message Request { + optional BackupRequest backup = 1; + optional RestoreRequest restore = 2; + optional DeleteRequest delete = 3; +} + +message Response { + optional BackupResponse backup = 1; + optional RestoreResponse restore = 2; + optional DeleteResponse delete = 3; +} + +// +// backup +// + +message BackupRequest { + optional bytes service_id = 1; + optional bytes backup_id = 2; + optional bytes nonce = 3; + optional uint64 valid_from = 4; + optional bytes data = 5; + optional bytes pin = 6; + optional uint32 tries = 7; +} + +message BackupResponse { + enum Status { + OK = 1; + ALREADY_EXISTS = 2; + NOT_YET_VALID = 3; + } + + optional Status status = 1; + optional bytes nonce = 2; +} + +// +// restore +// + +message RestoreRequest { + optional bytes service_id = 1; + optional bytes backup_id = 2; + optional bytes nonce = 3; + optional uint64 valid_from = 4; + optional bytes pin = 5; +} + +message RestoreResponse { + enum Status { + OK = 1; + NONCE_MISMATCH = 2; + NOT_YET_VALID = 3; + MISSING = 4; + PIN_MISMATCH = 5; + } + + optional Status status = 1; + optional bytes nonce = 2; + optional bytes data = 3; + optional uint32 tries = 4; +} + +// +// delete +// + +message DeleteRequest { + optional bytes service_id = 1; + optional bytes backup_id = 2; +} + +message DeleteResponse { +} diff --git a/enclave/include/kbupd_enclave.edl b/enclave/include/kbupd_enclave.edl new file mode 100644 index 0000000..00d141d --- /dev/null +++ b/enclave/include/kbupd_enclave.edl @@ -0,0 +1,16 @@ +enclave { + include "kbupd_sgxsd_callbacks.h" + from "sgxsd.edl" import *; + from "sgx_tstdc.edl" import *; + + trusted { + public void kbupd_enclave_recv_untrusted_msg + ([in, size=data_size] const uint8_t *data, size_t data_size); + }; + untrusted { + void kbupd_enclave_ocall_recv_enclave_msg + ([in, size=data_size] const uint8_t *data, size_t data_size); + void *kbupd_enclave_ocall_alloc([in, out] size_t *size); + void kbupd_enclave_ocall_panic([in, size=msg_size] const uint8_t *msg, size_t msg_size); + }; +}; diff --git a/enclave/include/kbupd_enclave_t.c b/enclave/include/kbupd_enclave_t.c new file mode 100644 index 0000000..b000926 --- /dev/null +++ b/enclave/include/kbupd_enclave_t.c @@ -0,0 +1,949 @@ +#include "kbupd_enclave_t.h" + +#include "sgx_trts.h" /* for sgx_ocalloc, sgx_is_outside_enclave */ +#include "sgx_lfence.h" /* for sgx_lfence */ + +#include +#include /* for memcpy_s etc */ +#include /* for malloc/free etc */ + +#define CHECK_REF_POINTER(ptr, siz) do { \ + if (!(ptr) || ! sgx_is_outside_enclave((ptr), (siz))) \ + return SGX_ERROR_INVALID_PARAMETER;\ +} while (0) + +#define CHECK_UNIQUE_POINTER(ptr, siz) do { \ + if ((ptr) && ! sgx_is_outside_enclave((ptr), (siz))) \ + return SGX_ERROR_INVALID_PARAMETER;\ +} while (0) + +#define CHECK_ENCLAVE_POINTER(ptr, siz) do { \ + if ((ptr) && ! sgx_is_within_enclave((ptr), (siz))) \ + return SGX_ERROR_INVALID_PARAMETER;\ +} while (0) + +#define ADD_ASSIGN_OVERFLOW(a, b) ( \ + ((a) += (b)) < (b) \ +) + + +typedef struct ms_kbupd_enclave_recv_untrusted_msg_t { + const uint8_t* ms_data; + size_t ms_data_size; +} ms_kbupd_enclave_recv_untrusted_msg_t; + +typedef struct ms_sgxsd_enclave_node_init_t { + sgx_status_t ms_retval; + const sgxsd_node_init_args_t* ms_p_args; +} ms_sgxsd_enclave_node_init_t; + +typedef struct ms_sgxsd_enclave_get_next_report_t { + sgx_status_t ms_retval; + sgx_target_info_t ms_qe_target_info; + sgx_report_t* ms_p_report; +} ms_sgxsd_enclave_get_next_report_t; + +typedef struct ms_sgxsd_enclave_set_current_quote_t { + sgx_status_t ms_retval; +} ms_sgxsd_enclave_set_current_quote_t; + +typedef struct ms_sgxsd_enclave_negotiate_request_t { + sgx_status_t ms_retval; + const sgxsd_request_negotiation_request_t* ms_p_request; + sgxsd_request_negotiation_response_t* ms_p_response; +} ms_sgxsd_enclave_negotiate_request_t; + +typedef struct ms_sgxsd_enclave_server_start_t { + sgx_status_t ms_retval; + const sgxsd_server_init_args_t* ms_p_args; + sgxsd_server_state_handle_t ms_state_handle; +} ms_sgxsd_enclave_server_start_t; + +typedef struct ms_sgxsd_enclave_server_call_t { + sgx_status_t ms_retval; + const sgxsd_server_handle_call_args_t* ms_p_args; + const sgxsd_msg_header_t* ms_msg_header; + uint8_t* ms_msg_data; + size_t ms_msg_size; + sgxsd_msg_tag_t ms_msg_tag; + sgxsd_server_state_handle_t ms_state_handle; +} ms_sgxsd_enclave_server_call_t; + +typedef struct ms_sgxsd_enclave_server_stop_t { + sgx_status_t ms_retval; + const sgxsd_server_terminate_args_t* ms_p_args; + sgxsd_server_state_handle_t ms_state_handle; +} ms_sgxsd_enclave_server_stop_t; + +typedef struct ms_kbupd_enclave_ocall_recv_enclave_msg_t { + const uint8_t* ms_data; + size_t ms_data_size; +} ms_kbupd_enclave_ocall_recv_enclave_msg_t; + +typedef struct ms_kbupd_enclave_ocall_alloc_t { + void* ms_retval; + size_t* ms_size; +} ms_kbupd_enclave_ocall_alloc_t; + +typedef struct ms_kbupd_enclave_ocall_panic_t { + const uint8_t* ms_msg; + size_t ms_msg_size; +} ms_kbupd_enclave_ocall_panic_t; + +typedef struct ms_sgxsd_ocall_reply_t { + sgx_status_t ms_retval; + const sgxsd_msg_header_t* ms_reply_header; + const uint8_t* ms_reply_data; + size_t ms_reply_data_size; + sgxsd_msg_tag_t ms_msg_tag; +} ms_sgxsd_ocall_reply_t; + +typedef struct ms_sgx_oc_cpuidex_t { + int* ms_cpuinfo; + int ms_leaf; + int ms_subleaf; +} ms_sgx_oc_cpuidex_t; + +typedef struct ms_sgx_thread_wait_untrusted_event_ocall_t { + int ms_retval; + const void* ms_self; +} ms_sgx_thread_wait_untrusted_event_ocall_t; + +typedef struct ms_sgx_thread_set_untrusted_event_ocall_t { + int ms_retval; + const void* ms_waiter; +} ms_sgx_thread_set_untrusted_event_ocall_t; + +typedef struct ms_sgx_thread_setwait_untrusted_events_ocall_t { + int ms_retval; + const void* ms_waiter; + const void* ms_self; +} ms_sgx_thread_setwait_untrusted_events_ocall_t; + +typedef struct ms_sgx_thread_set_multiple_untrusted_events_ocall_t { + int ms_retval; + const void** ms_waiters; + size_t ms_total; +} ms_sgx_thread_set_multiple_untrusted_events_ocall_t; + +static sgx_status_t SGX_CDECL sgx_kbupd_enclave_recv_untrusted_msg(void* pms) +{ + CHECK_REF_POINTER(pms, sizeof(ms_kbupd_enclave_recv_untrusted_msg_t)); + // + // fence after pointer checks + // + sgx_lfence(); + ms_kbupd_enclave_recv_untrusted_msg_t* ms = SGX_CAST(ms_kbupd_enclave_recv_untrusted_msg_t*, pms); + sgx_status_t status = SGX_SUCCESS; + const uint8_t* _tmp_data = ms->ms_data; + size_t _tmp_data_size = ms->ms_data_size; + size_t _len_data = _tmp_data_size; + uint8_t* _in_data = NULL; + + CHECK_UNIQUE_POINTER(_tmp_data, _len_data); + + // + // fence after pointer checks + // + sgx_lfence(); + + if (_tmp_data != NULL && _len_data != 0) { + if ( _len_data % sizeof(*_tmp_data) != 0) + { + status = SGX_ERROR_INVALID_PARAMETER; + goto err; + } + _in_data = (uint8_t*)malloc(_len_data); + if (_in_data == NULL) { + status = SGX_ERROR_OUT_OF_MEMORY; + goto err; + } + + if (memcpy_s(_in_data, _len_data, _tmp_data, _len_data)) { + status = SGX_ERROR_UNEXPECTED; + goto err; + } + + } + + kbupd_enclave_recv_untrusted_msg((const uint8_t*)_in_data, _tmp_data_size); + +err: + if (_in_data) free(_in_data); + return status; +} + +static sgx_status_t SGX_CDECL sgx_sgxsd_enclave_node_init(void* pms) +{ + CHECK_REF_POINTER(pms, sizeof(ms_sgxsd_enclave_node_init_t)); + // + // fence after pointer checks + // + sgx_lfence(); + ms_sgxsd_enclave_node_init_t* ms = SGX_CAST(ms_sgxsd_enclave_node_init_t*, pms); + sgx_status_t status = SGX_SUCCESS; + const sgxsd_node_init_args_t* _tmp_p_args = ms->ms_p_args; + size_t _len_p_args = sizeof(sgxsd_node_init_args_t); + sgxsd_node_init_args_t* _in_p_args = NULL; + + CHECK_UNIQUE_POINTER(_tmp_p_args, _len_p_args); + + // + // fence after pointer checks + // + sgx_lfence(); + + if (_tmp_p_args != NULL && _len_p_args != 0) { + _in_p_args = (sgxsd_node_init_args_t*)malloc(_len_p_args); + if (_in_p_args == NULL) { + status = SGX_ERROR_OUT_OF_MEMORY; + goto err; + } + + if (memcpy_s(_in_p_args, _len_p_args, _tmp_p_args, _len_p_args)) { + status = SGX_ERROR_UNEXPECTED; + goto err; + } + + } + + ms->ms_retval = sgxsd_enclave_node_init((const sgxsd_node_init_args_t*)_in_p_args); + +err: + if (_in_p_args) free(_in_p_args); + return status; +} + +static sgx_status_t SGX_CDECL sgx_sgxsd_enclave_get_next_report(void* pms) +{ + CHECK_REF_POINTER(pms, sizeof(ms_sgxsd_enclave_get_next_report_t)); + // + // fence after pointer checks + // + sgx_lfence(); + ms_sgxsd_enclave_get_next_report_t* ms = SGX_CAST(ms_sgxsd_enclave_get_next_report_t*, pms); + sgx_status_t status = SGX_SUCCESS; + sgx_report_t* _tmp_p_report = ms->ms_p_report; + size_t _len_p_report = sizeof(sgx_report_t); + sgx_report_t* _in_p_report = NULL; + + CHECK_UNIQUE_POINTER(_tmp_p_report, _len_p_report); + + // + // fence after pointer checks + // + sgx_lfence(); + + if (_tmp_p_report != NULL && _len_p_report != 0) { + if ((_in_p_report = (sgx_report_t*)malloc(_len_p_report)) == NULL) { + status = SGX_ERROR_OUT_OF_MEMORY; + goto err; + } + + memset((void*)_in_p_report, 0, _len_p_report); + } + + ms->ms_retval = sgxsd_enclave_get_next_report(ms->ms_qe_target_info, _in_p_report); + if (_in_p_report) { + if (memcpy_s(_tmp_p_report, _len_p_report, _in_p_report, _len_p_report)) { + status = SGX_ERROR_UNEXPECTED; + goto err; + } + } + +err: + if (_in_p_report) free(_in_p_report); + return status; +} + +static sgx_status_t SGX_CDECL sgx_sgxsd_enclave_set_current_quote(void* pms) +{ + CHECK_REF_POINTER(pms, sizeof(ms_sgxsd_enclave_set_current_quote_t)); + // + // fence after pointer checks + // + sgx_lfence(); + ms_sgxsd_enclave_set_current_quote_t* ms = SGX_CAST(ms_sgxsd_enclave_set_current_quote_t*, pms); + sgx_status_t status = SGX_SUCCESS; + + + + ms->ms_retval = sgxsd_enclave_set_current_quote(); + + + return status; +} + +static sgx_status_t SGX_CDECL sgx_sgxsd_enclave_negotiate_request(void* pms) +{ + CHECK_REF_POINTER(pms, sizeof(ms_sgxsd_enclave_negotiate_request_t)); + // + // fence after pointer checks + // + sgx_lfence(); + ms_sgxsd_enclave_negotiate_request_t* ms = SGX_CAST(ms_sgxsd_enclave_negotiate_request_t*, pms); + sgx_status_t status = SGX_SUCCESS; + const sgxsd_request_negotiation_request_t* _tmp_p_request = ms->ms_p_request; + size_t _len_p_request = sizeof(sgxsd_request_negotiation_request_t); + sgxsd_request_negotiation_request_t* _in_p_request = NULL; + sgxsd_request_negotiation_response_t* _tmp_p_response = ms->ms_p_response; + size_t _len_p_response = sizeof(sgxsd_request_negotiation_response_t); + sgxsd_request_negotiation_response_t* _in_p_response = NULL; + + CHECK_UNIQUE_POINTER(_tmp_p_request, _len_p_request); + CHECK_UNIQUE_POINTER(_tmp_p_response, _len_p_response); + + // + // fence after pointer checks + // + sgx_lfence(); + + if (_tmp_p_request != NULL && _len_p_request != 0) { + _in_p_request = (sgxsd_request_negotiation_request_t*)malloc(_len_p_request); + if (_in_p_request == NULL) { + status = SGX_ERROR_OUT_OF_MEMORY; + goto err; + } + + if (memcpy_s(_in_p_request, _len_p_request, _tmp_p_request, _len_p_request)) { + status = SGX_ERROR_UNEXPECTED; + goto err; + } + + } + if (_tmp_p_response != NULL && _len_p_response != 0) { + if ((_in_p_response = (sgxsd_request_negotiation_response_t*)malloc(_len_p_response)) == NULL) { + status = SGX_ERROR_OUT_OF_MEMORY; + goto err; + } + + memset((void*)_in_p_response, 0, _len_p_response); + } + + ms->ms_retval = sgxsd_enclave_negotiate_request((const sgxsd_request_negotiation_request_t*)_in_p_request, _in_p_response); + if (_in_p_response) { + if (memcpy_s(_tmp_p_response, _len_p_response, _in_p_response, _len_p_response)) { + status = SGX_ERROR_UNEXPECTED; + goto err; + } + } + +err: + if (_in_p_request) free(_in_p_request); + if (_in_p_response) free(_in_p_response); + return status; +} + +static sgx_status_t SGX_CDECL sgx_sgxsd_enclave_server_start(void* pms) +{ + CHECK_REF_POINTER(pms, sizeof(ms_sgxsd_enclave_server_start_t)); + // + // fence after pointer checks + // + sgx_lfence(); + ms_sgxsd_enclave_server_start_t* ms = SGX_CAST(ms_sgxsd_enclave_server_start_t*, pms); + sgx_status_t status = SGX_SUCCESS; + const sgxsd_server_init_args_t* _tmp_p_args = ms->ms_p_args; + size_t _len_p_args = sizeof(sgxsd_server_init_args_t); + sgxsd_server_init_args_t* _in_p_args = NULL; + + CHECK_UNIQUE_POINTER(_tmp_p_args, _len_p_args); + + // + // fence after pointer checks + // + sgx_lfence(); + + if (_tmp_p_args != NULL && _len_p_args != 0) { + _in_p_args = (sgxsd_server_init_args_t*)malloc(_len_p_args); + if (_in_p_args == NULL) { + status = SGX_ERROR_OUT_OF_MEMORY; + goto err; + } + + if (memcpy_s(_in_p_args, _len_p_args, _tmp_p_args, _len_p_args)) { + status = SGX_ERROR_UNEXPECTED; + goto err; + } + + } + + ms->ms_retval = sgxsd_enclave_server_start((const sgxsd_server_init_args_t*)_in_p_args, ms->ms_state_handle); + +err: + if (_in_p_args) free(_in_p_args); + return status; +} + +static sgx_status_t SGX_CDECL sgx_sgxsd_enclave_server_call(void* pms) +{ + CHECK_REF_POINTER(pms, sizeof(ms_sgxsd_enclave_server_call_t)); + // + // fence after pointer checks + // + sgx_lfence(); + ms_sgxsd_enclave_server_call_t* ms = SGX_CAST(ms_sgxsd_enclave_server_call_t*, pms); + sgx_status_t status = SGX_SUCCESS; + const sgxsd_server_handle_call_args_t* _tmp_p_args = ms->ms_p_args; + size_t _len_p_args = sizeof(sgxsd_server_handle_call_args_t); + sgxsd_server_handle_call_args_t* _in_p_args = NULL; + const sgxsd_msg_header_t* _tmp_msg_header = ms->ms_msg_header; + size_t _len_msg_header = sizeof(sgxsd_msg_header_t); + sgxsd_msg_header_t* _in_msg_header = NULL; + uint8_t* _tmp_msg_data = ms->ms_msg_data; + size_t _tmp_msg_size = ms->ms_msg_size; + size_t _len_msg_data = _tmp_msg_size; + uint8_t* _in_msg_data = NULL; + + CHECK_UNIQUE_POINTER(_tmp_p_args, _len_p_args); + CHECK_UNIQUE_POINTER(_tmp_msg_header, _len_msg_header); + CHECK_UNIQUE_POINTER(_tmp_msg_data, _len_msg_data); + + // + // fence after pointer checks + // + sgx_lfence(); + + if (_tmp_p_args != NULL && _len_p_args != 0) { + _in_p_args = (sgxsd_server_handle_call_args_t*)malloc(_len_p_args); + if (_in_p_args == NULL) { + status = SGX_ERROR_OUT_OF_MEMORY; + goto err; + } + + if (memcpy_s(_in_p_args, _len_p_args, _tmp_p_args, _len_p_args)) { + status = SGX_ERROR_UNEXPECTED; + goto err; + } + + } + if (_tmp_msg_header != NULL && _len_msg_header != 0) { + _in_msg_header = (sgxsd_msg_header_t*)malloc(_len_msg_header); + if (_in_msg_header == NULL) { + status = SGX_ERROR_OUT_OF_MEMORY; + goto err; + } + + if (memcpy_s(_in_msg_header, _len_msg_header, _tmp_msg_header, _len_msg_header)) { + status = SGX_ERROR_UNEXPECTED; + goto err; + } + + } + if (_tmp_msg_data != NULL && _len_msg_data != 0) { + if ( _len_msg_data % sizeof(*_tmp_msg_data) != 0) + { + status = SGX_ERROR_INVALID_PARAMETER; + goto err; + } + _in_msg_data = (uint8_t*)malloc(_len_msg_data); + if (_in_msg_data == NULL) { + status = SGX_ERROR_OUT_OF_MEMORY; + goto err; + } + + if (memcpy_s(_in_msg_data, _len_msg_data, _tmp_msg_data, _len_msg_data)) { + status = SGX_ERROR_UNEXPECTED; + goto err; + } + + } + + ms->ms_retval = sgxsd_enclave_server_call((const sgxsd_server_handle_call_args_t*)_in_p_args, (const sgxsd_msg_header_t*)_in_msg_header, _in_msg_data, _tmp_msg_size, ms->ms_msg_tag, ms->ms_state_handle); + +err: + if (_in_p_args) free(_in_p_args); + if (_in_msg_header) free(_in_msg_header); + if (_in_msg_data) free(_in_msg_data); + return status; +} + +static sgx_status_t SGX_CDECL sgx_sgxsd_enclave_server_stop(void* pms) +{ + CHECK_REF_POINTER(pms, sizeof(ms_sgxsd_enclave_server_stop_t)); + // + // fence after pointer checks + // + sgx_lfence(); + ms_sgxsd_enclave_server_stop_t* ms = SGX_CAST(ms_sgxsd_enclave_server_stop_t*, pms); + sgx_status_t status = SGX_SUCCESS; + const sgxsd_server_terminate_args_t* _tmp_p_args = ms->ms_p_args; + size_t _len_p_args = sizeof(sgxsd_server_terminate_args_t); + sgxsd_server_terminate_args_t* _in_p_args = NULL; + + CHECK_UNIQUE_POINTER(_tmp_p_args, _len_p_args); + + // + // fence after pointer checks + // + sgx_lfence(); + + if (_tmp_p_args != NULL && _len_p_args != 0) { + _in_p_args = (sgxsd_server_terminate_args_t*)malloc(_len_p_args); + if (_in_p_args == NULL) { + status = SGX_ERROR_OUT_OF_MEMORY; + goto err; + } + + if (memcpy_s(_in_p_args, _len_p_args, _tmp_p_args, _len_p_args)) { + status = SGX_ERROR_UNEXPECTED; + goto err; + } + + } + + ms->ms_retval = sgxsd_enclave_server_stop((const sgxsd_server_terminate_args_t*)_in_p_args, ms->ms_state_handle); + +err: + if (_in_p_args) free(_in_p_args); + return status; +} + +SGX_EXTERNC const struct { + size_t nr_ecall; + struct {void* ecall_addr; uint8_t is_priv;} ecall_table[8]; +} g_ecall_table = { + 8, + { + {(void*)(uintptr_t)sgx_kbupd_enclave_recv_untrusted_msg, 0}, + {(void*)(uintptr_t)sgx_sgxsd_enclave_node_init, 0}, + {(void*)(uintptr_t)sgx_sgxsd_enclave_get_next_report, 0}, + {(void*)(uintptr_t)sgx_sgxsd_enclave_set_current_quote, 0}, + {(void*)(uintptr_t)sgx_sgxsd_enclave_negotiate_request, 0}, + {(void*)(uintptr_t)sgx_sgxsd_enclave_server_start, 0}, + {(void*)(uintptr_t)sgx_sgxsd_enclave_server_call, 0}, + {(void*)(uintptr_t)sgx_sgxsd_enclave_server_stop, 0}, + } +}; + +SGX_EXTERNC const struct { + size_t nr_ocall; + uint8_t entry_table[9][8]; +} g_dyn_entry_table = { + 9, + { + {0, 0, 0, 0, 0, 0, 0, 0, }, + {0, 0, 0, 0, 0, 0, 0, 0, }, + {0, 0, 0, 0, 0, 0, 0, 0, }, + {0, 0, 0, 0, 0, 0, 0, 0, }, + {0, 0, 0, 0, 0, 0, 0, 0, }, + {0, 0, 0, 0, 0, 0, 0, 0, }, + {0, 0, 0, 0, 0, 0, 0, 0, }, + {0, 0, 0, 0, 0, 0, 0, 0, }, + {0, 0, 0, 0, 0, 0, 0, 0, }, + } +}; + + +sgx_status_t SGX_CDECL kbupd_enclave_ocall_recv_enclave_msg(const uint8_t* data, size_t data_size) +{ + sgx_status_t status = SGX_SUCCESS; + size_t _len_data = data_size; + + ms_kbupd_enclave_ocall_recv_enclave_msg_t* ms = NULL; + size_t ocalloc_size = sizeof(ms_kbupd_enclave_ocall_recv_enclave_msg_t); + void *__tmp = NULL; + + + CHECK_ENCLAVE_POINTER(data, _len_data); + + if (ADD_ASSIGN_OVERFLOW(ocalloc_size, (data != NULL) ? _len_data : 0)) + return SGX_ERROR_INVALID_PARAMETER; + + __tmp = sgx_ocalloc(ocalloc_size); + if (__tmp == NULL) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + ms = (ms_kbupd_enclave_ocall_recv_enclave_msg_t*)__tmp; + __tmp = (void *)((size_t)__tmp + sizeof(ms_kbupd_enclave_ocall_recv_enclave_msg_t)); + ocalloc_size -= sizeof(ms_kbupd_enclave_ocall_recv_enclave_msg_t); + + if (data != NULL) { + ms->ms_data = (const uint8_t*)__tmp; + if (_len_data % sizeof(*data) != 0) { + sgx_ocfree(); + return SGX_ERROR_INVALID_PARAMETER; + } + if (memcpy_s(__tmp, ocalloc_size, data, _len_data)) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + __tmp = (void *)((size_t)__tmp + _len_data); + ocalloc_size -= _len_data; + } else { + ms->ms_data = NULL; + } + + ms->ms_data_size = data_size; + status = sgx_ocall(0, ms); + + if (status == SGX_SUCCESS) { + } + sgx_ocfree(); + return status; +} + +sgx_status_t SGX_CDECL kbupd_enclave_ocall_alloc(void** retval, size_t* size) +{ + sgx_status_t status = SGX_SUCCESS; + size_t _len_size = sizeof(size_t); + + ms_kbupd_enclave_ocall_alloc_t* ms = NULL; + size_t ocalloc_size = sizeof(ms_kbupd_enclave_ocall_alloc_t); + void *__tmp = NULL; + + void *__tmp_size = NULL; + + CHECK_ENCLAVE_POINTER(size, _len_size); + + if (ADD_ASSIGN_OVERFLOW(ocalloc_size, (size != NULL) ? _len_size : 0)) + return SGX_ERROR_INVALID_PARAMETER; + + __tmp = sgx_ocalloc(ocalloc_size); + if (__tmp == NULL) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + ms = (ms_kbupd_enclave_ocall_alloc_t*)__tmp; + __tmp = (void *)((size_t)__tmp + sizeof(ms_kbupd_enclave_ocall_alloc_t)); + ocalloc_size -= sizeof(ms_kbupd_enclave_ocall_alloc_t); + + if (size != NULL) { + ms->ms_size = (size_t*)__tmp; + __tmp_size = __tmp; + if (_len_size % sizeof(*size) != 0) { + sgx_ocfree(); + return SGX_ERROR_INVALID_PARAMETER; + } + if (memcpy_s(__tmp_size, ocalloc_size, size, _len_size)) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + __tmp = (void *)((size_t)__tmp + _len_size); + ocalloc_size -= _len_size; + } else { + ms->ms_size = NULL; + } + + status = sgx_ocall(1, ms); + + if (status == SGX_SUCCESS) { + if (retval) *retval = ms->ms_retval; + if (size) { + if (memcpy_s((void*)size, _len_size, __tmp_size, _len_size)) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + } + } + sgx_ocfree(); + return status; +} + +sgx_status_t SGX_CDECL kbupd_enclave_ocall_panic(const uint8_t* msg, size_t msg_size) +{ + sgx_status_t status = SGX_SUCCESS; + size_t _len_msg = msg_size; + + ms_kbupd_enclave_ocall_panic_t* ms = NULL; + size_t ocalloc_size = sizeof(ms_kbupd_enclave_ocall_panic_t); + void *__tmp = NULL; + + + CHECK_ENCLAVE_POINTER(msg, _len_msg); + + if (ADD_ASSIGN_OVERFLOW(ocalloc_size, (msg != NULL) ? _len_msg : 0)) + return SGX_ERROR_INVALID_PARAMETER; + + __tmp = sgx_ocalloc(ocalloc_size); + if (__tmp == NULL) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + ms = (ms_kbupd_enclave_ocall_panic_t*)__tmp; + __tmp = (void *)((size_t)__tmp + sizeof(ms_kbupd_enclave_ocall_panic_t)); + ocalloc_size -= sizeof(ms_kbupd_enclave_ocall_panic_t); + + if (msg != NULL) { + ms->ms_msg = (const uint8_t*)__tmp; + if (_len_msg % sizeof(*msg) != 0) { + sgx_ocfree(); + return SGX_ERROR_INVALID_PARAMETER; + } + if (memcpy_s(__tmp, ocalloc_size, msg, _len_msg)) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + __tmp = (void *)((size_t)__tmp + _len_msg); + ocalloc_size -= _len_msg; + } else { + ms->ms_msg = NULL; + } + + ms->ms_msg_size = msg_size; + status = sgx_ocall(2, ms); + + if (status == SGX_SUCCESS) { + } + sgx_ocfree(); + return status; +} + +sgx_status_t SGX_CDECL sgxsd_ocall_reply(sgx_status_t* retval, const sgxsd_msg_header_t* reply_header, const uint8_t* reply_data, size_t reply_data_size, sgxsd_msg_tag_t msg_tag) +{ + sgx_status_t status = SGX_SUCCESS; + size_t _len_reply_header = sizeof(sgxsd_msg_header_t); + size_t _len_reply_data = reply_data_size; + + ms_sgxsd_ocall_reply_t* ms = NULL; + size_t ocalloc_size = sizeof(ms_sgxsd_ocall_reply_t); + void *__tmp = NULL; + + + CHECK_ENCLAVE_POINTER(reply_header, _len_reply_header); + CHECK_ENCLAVE_POINTER(reply_data, _len_reply_data); + + if (ADD_ASSIGN_OVERFLOW(ocalloc_size, (reply_header != NULL) ? _len_reply_header : 0)) + return SGX_ERROR_INVALID_PARAMETER; + if (ADD_ASSIGN_OVERFLOW(ocalloc_size, (reply_data != NULL) ? _len_reply_data : 0)) + return SGX_ERROR_INVALID_PARAMETER; + + __tmp = sgx_ocalloc(ocalloc_size); + if (__tmp == NULL) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + ms = (ms_sgxsd_ocall_reply_t*)__tmp; + __tmp = (void *)((size_t)__tmp + sizeof(ms_sgxsd_ocall_reply_t)); + ocalloc_size -= sizeof(ms_sgxsd_ocall_reply_t); + + if (reply_header != NULL) { + ms->ms_reply_header = (const sgxsd_msg_header_t*)__tmp; + if (memcpy_s(__tmp, ocalloc_size, reply_header, _len_reply_header)) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + __tmp = (void *)((size_t)__tmp + _len_reply_header); + ocalloc_size -= _len_reply_header; + } else { + ms->ms_reply_header = NULL; + } + + if (reply_data != NULL) { + ms->ms_reply_data = (const uint8_t*)__tmp; + if (_len_reply_data % sizeof(*reply_data) != 0) { + sgx_ocfree(); + return SGX_ERROR_INVALID_PARAMETER; + } + if (memcpy_s(__tmp, ocalloc_size, reply_data, _len_reply_data)) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + __tmp = (void *)((size_t)__tmp + _len_reply_data); + ocalloc_size -= _len_reply_data; + } else { + ms->ms_reply_data = NULL; + } + + ms->ms_reply_data_size = reply_data_size; + ms->ms_msg_tag = msg_tag; + status = sgx_ocall(3, ms); + + if (status == SGX_SUCCESS) { + if (retval) *retval = ms->ms_retval; + } + sgx_ocfree(); + return status; +} + +sgx_status_t SGX_CDECL sgx_oc_cpuidex(int cpuinfo[4], int leaf, int subleaf) +{ + sgx_status_t status = SGX_SUCCESS; + size_t _len_cpuinfo = 4 * sizeof(int); + + ms_sgx_oc_cpuidex_t* ms = NULL; + size_t ocalloc_size = sizeof(ms_sgx_oc_cpuidex_t); + void *__tmp = NULL; + + void *__tmp_cpuinfo = NULL; + + CHECK_ENCLAVE_POINTER(cpuinfo, _len_cpuinfo); + + if (ADD_ASSIGN_OVERFLOW(ocalloc_size, (cpuinfo != NULL) ? _len_cpuinfo : 0)) + return SGX_ERROR_INVALID_PARAMETER; + + __tmp = sgx_ocalloc(ocalloc_size); + if (__tmp == NULL) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + ms = (ms_sgx_oc_cpuidex_t*)__tmp; + __tmp = (void *)((size_t)__tmp + sizeof(ms_sgx_oc_cpuidex_t)); + ocalloc_size -= sizeof(ms_sgx_oc_cpuidex_t); + + if (cpuinfo != NULL) { + ms->ms_cpuinfo = (int*)__tmp; + __tmp_cpuinfo = __tmp; + if (_len_cpuinfo % sizeof(*cpuinfo) != 0) { + sgx_ocfree(); + return SGX_ERROR_INVALID_PARAMETER; + } + memset(__tmp_cpuinfo, 0, _len_cpuinfo); + __tmp = (void *)((size_t)__tmp + _len_cpuinfo); + ocalloc_size -= _len_cpuinfo; + } else { + ms->ms_cpuinfo = NULL; + } + + ms->ms_leaf = leaf; + ms->ms_subleaf = subleaf; + status = sgx_ocall(4, ms); + + if (status == SGX_SUCCESS) { + if (cpuinfo) { + if (memcpy_s((void*)cpuinfo, _len_cpuinfo, __tmp_cpuinfo, _len_cpuinfo)) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + } + } + sgx_ocfree(); + return status; +} + +sgx_status_t SGX_CDECL sgx_thread_wait_untrusted_event_ocall(int* retval, const void* self) +{ + sgx_status_t status = SGX_SUCCESS; + + ms_sgx_thread_wait_untrusted_event_ocall_t* ms = NULL; + size_t ocalloc_size = sizeof(ms_sgx_thread_wait_untrusted_event_ocall_t); + void *__tmp = NULL; + + + __tmp = sgx_ocalloc(ocalloc_size); + if (__tmp == NULL) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + ms = (ms_sgx_thread_wait_untrusted_event_ocall_t*)__tmp; + __tmp = (void *)((size_t)__tmp + sizeof(ms_sgx_thread_wait_untrusted_event_ocall_t)); + ocalloc_size -= sizeof(ms_sgx_thread_wait_untrusted_event_ocall_t); + + ms->ms_self = self; + status = sgx_ocall(5, ms); + + if (status == SGX_SUCCESS) { + if (retval) *retval = ms->ms_retval; + } + sgx_ocfree(); + return status; +} + +sgx_status_t SGX_CDECL sgx_thread_set_untrusted_event_ocall(int* retval, const void* waiter) +{ + sgx_status_t status = SGX_SUCCESS; + + ms_sgx_thread_set_untrusted_event_ocall_t* ms = NULL; + size_t ocalloc_size = sizeof(ms_sgx_thread_set_untrusted_event_ocall_t); + void *__tmp = NULL; + + + __tmp = sgx_ocalloc(ocalloc_size); + if (__tmp == NULL) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + ms = (ms_sgx_thread_set_untrusted_event_ocall_t*)__tmp; + __tmp = (void *)((size_t)__tmp + sizeof(ms_sgx_thread_set_untrusted_event_ocall_t)); + ocalloc_size -= sizeof(ms_sgx_thread_set_untrusted_event_ocall_t); + + ms->ms_waiter = waiter; + status = sgx_ocall(6, ms); + + if (status == SGX_SUCCESS) { + if (retval) *retval = ms->ms_retval; + } + sgx_ocfree(); + return status; +} + +sgx_status_t SGX_CDECL sgx_thread_setwait_untrusted_events_ocall(int* retval, const void* waiter, const void* self) +{ + sgx_status_t status = SGX_SUCCESS; + + ms_sgx_thread_setwait_untrusted_events_ocall_t* ms = NULL; + size_t ocalloc_size = sizeof(ms_sgx_thread_setwait_untrusted_events_ocall_t); + void *__tmp = NULL; + + + __tmp = sgx_ocalloc(ocalloc_size); + if (__tmp == NULL) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + ms = (ms_sgx_thread_setwait_untrusted_events_ocall_t*)__tmp; + __tmp = (void *)((size_t)__tmp + sizeof(ms_sgx_thread_setwait_untrusted_events_ocall_t)); + ocalloc_size -= sizeof(ms_sgx_thread_setwait_untrusted_events_ocall_t); + + ms->ms_waiter = waiter; + ms->ms_self = self; + status = sgx_ocall(7, ms); + + if (status == SGX_SUCCESS) { + if (retval) *retval = ms->ms_retval; + } + sgx_ocfree(); + return status; +} + +sgx_status_t SGX_CDECL sgx_thread_set_multiple_untrusted_events_ocall(int* retval, const void** waiters, size_t total) +{ + sgx_status_t status = SGX_SUCCESS; + size_t _len_waiters = total * sizeof(void*); + + ms_sgx_thread_set_multiple_untrusted_events_ocall_t* ms = NULL; + size_t ocalloc_size = sizeof(ms_sgx_thread_set_multiple_untrusted_events_ocall_t); + void *__tmp = NULL; + + + CHECK_ENCLAVE_POINTER(waiters, _len_waiters); + + if (ADD_ASSIGN_OVERFLOW(ocalloc_size, (waiters != NULL) ? _len_waiters : 0)) + return SGX_ERROR_INVALID_PARAMETER; + + __tmp = sgx_ocalloc(ocalloc_size); + if (__tmp == NULL) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + ms = (ms_sgx_thread_set_multiple_untrusted_events_ocall_t*)__tmp; + __tmp = (void *)((size_t)__tmp + sizeof(ms_sgx_thread_set_multiple_untrusted_events_ocall_t)); + ocalloc_size -= sizeof(ms_sgx_thread_set_multiple_untrusted_events_ocall_t); + + if (waiters != NULL) { + ms->ms_waiters = (const void**)__tmp; + if (_len_waiters % sizeof(*waiters) != 0) { + sgx_ocfree(); + return SGX_ERROR_INVALID_PARAMETER; + } + if (memcpy_s(__tmp, ocalloc_size, waiters, _len_waiters)) { + sgx_ocfree(); + return SGX_ERROR_UNEXPECTED; + } + __tmp = (void *)((size_t)__tmp + _len_waiters); + ocalloc_size -= _len_waiters; + } else { + ms->ms_waiters = NULL; + } + + ms->ms_total = total; + status = sgx_ocall(8, ms); + + if (status == SGX_SUCCESS) { + if (retval) *retval = ms->ms_retval; + } + sgx_ocfree(); + return status; +} + diff --git a/enclave/include/kbupd_enclave_t.h b/enclave/include/kbupd_enclave_t.h new file mode 100644 index 0000000..19f84fa --- /dev/null +++ b/enclave/include/kbupd_enclave_t.h @@ -0,0 +1,46 @@ +#ifndef KBUPD_ENCLAVE_T_H__ +#define KBUPD_ENCLAVE_T_H__ + +#include +#include +#include +#include "sgx_edger8r.h" /* for sgx_ocall etc. */ + +#include "kbupd_sgxsd_callbacks.h" +#include "stdbool.h" +#include "sgx_quote.h" +#include "sgx_report.h" +#include "sgxsd.h" + +#include /* for size_t */ + +#define SGX_CAST(type, item) ((type)(item)) + +#ifdef __cplusplus +extern "C" { +#endif + +void kbupd_enclave_recv_untrusted_msg(const uint8_t* data, size_t data_size); +sgx_status_t sgxsd_enclave_node_init(const sgxsd_node_init_args_t* p_args); +sgx_status_t sgxsd_enclave_get_next_report(sgx_target_info_t qe_target_info, sgx_report_t* p_report); +sgx_status_t sgxsd_enclave_set_current_quote(void); +sgx_status_t sgxsd_enclave_negotiate_request(const sgxsd_request_negotiation_request_t* p_request, sgxsd_request_negotiation_response_t* p_response); +sgx_status_t sgxsd_enclave_server_start(const sgxsd_server_init_args_t* p_args, sgxsd_server_state_handle_t state_handle); +sgx_status_t sgxsd_enclave_server_call(const sgxsd_server_handle_call_args_t* p_args, const sgxsd_msg_header_t* msg_header, uint8_t* msg_data, size_t msg_size, sgxsd_msg_tag_t msg_tag, sgxsd_server_state_handle_t state_handle); +sgx_status_t sgxsd_enclave_server_stop(const sgxsd_server_terminate_args_t* p_args, sgxsd_server_state_handle_t state_handle); + +sgx_status_t SGX_CDECL kbupd_enclave_ocall_recv_enclave_msg(const uint8_t* data, size_t data_size); +sgx_status_t SGX_CDECL kbupd_enclave_ocall_alloc(void** retval, size_t* size); +sgx_status_t SGX_CDECL kbupd_enclave_ocall_panic(const uint8_t* msg, size_t msg_size); +sgx_status_t SGX_CDECL sgxsd_ocall_reply(sgx_status_t* retval, const sgxsd_msg_header_t* reply_header, const uint8_t* reply_data, size_t reply_data_size, sgxsd_msg_tag_t msg_tag); +sgx_status_t SGX_CDECL sgx_oc_cpuidex(int cpuinfo[4], int leaf, int subleaf); +sgx_status_t SGX_CDECL sgx_thread_wait_untrusted_event_ocall(int* retval, const void* self); +sgx_status_t SGX_CDECL sgx_thread_set_untrusted_event_ocall(int* retval, const void* waiter); +sgx_status_t SGX_CDECL sgx_thread_setwait_untrusted_events_ocall(int* retval, const void* waiter, const void* self); +sgx_status_t SGX_CDECL sgx_thread_set_multiple_untrusted_events_ocall(int* retval, const void** waiters, size_t total); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/enclave/include/kbupd_enclave_u.c b/enclave/include/kbupd_enclave_u.c new file mode 100644 index 0000000..d8b0f81 --- /dev/null +++ b/enclave/include/kbupd_enclave_u.c @@ -0,0 +1,279 @@ +#include "kbupd_enclave_u.h" +#include + +typedef struct ms_kbupd_enclave_recv_untrusted_msg_t { + const uint8_t* ms_data; + size_t ms_data_size; +} ms_kbupd_enclave_recv_untrusted_msg_t; + +typedef struct ms_sgxsd_enclave_node_init_t { + sgx_status_t ms_retval; + const sgxsd_node_init_args_t* ms_p_args; +} ms_sgxsd_enclave_node_init_t; + +typedef struct ms_sgxsd_enclave_get_next_report_t { + sgx_status_t ms_retval; + sgx_target_info_t ms_qe_target_info; + sgx_report_t* ms_p_report; +} ms_sgxsd_enclave_get_next_report_t; + +typedef struct ms_sgxsd_enclave_set_current_quote_t { + sgx_status_t ms_retval; +} ms_sgxsd_enclave_set_current_quote_t; + +typedef struct ms_sgxsd_enclave_negotiate_request_t { + sgx_status_t ms_retval; + const sgxsd_request_negotiation_request_t* ms_p_request; + sgxsd_request_negotiation_response_t* ms_p_response; +} ms_sgxsd_enclave_negotiate_request_t; + +typedef struct ms_sgxsd_enclave_server_start_t { + sgx_status_t ms_retval; + const sgxsd_server_init_args_t* ms_p_args; + sgxsd_server_state_handle_t ms_state_handle; +} ms_sgxsd_enclave_server_start_t; + +typedef struct ms_sgxsd_enclave_server_call_t { + sgx_status_t ms_retval; + const sgxsd_server_handle_call_args_t* ms_p_args; + const sgxsd_msg_header_t* ms_msg_header; + uint8_t* ms_msg_data; + size_t ms_msg_size; + sgxsd_msg_tag_t ms_msg_tag; + sgxsd_server_state_handle_t ms_state_handle; +} ms_sgxsd_enclave_server_call_t; + +typedef struct ms_sgxsd_enclave_server_stop_t { + sgx_status_t ms_retval; + const sgxsd_server_terminate_args_t* ms_p_args; + sgxsd_server_state_handle_t ms_state_handle; +} ms_sgxsd_enclave_server_stop_t; + +typedef struct ms_kbupd_enclave_ocall_recv_enclave_msg_t { + const uint8_t* ms_data; + size_t ms_data_size; +} ms_kbupd_enclave_ocall_recv_enclave_msg_t; + +typedef struct ms_kbupd_enclave_ocall_alloc_t { + void* ms_retval; + size_t* ms_size; +} ms_kbupd_enclave_ocall_alloc_t; + +typedef struct ms_kbupd_enclave_ocall_panic_t { + const uint8_t* ms_msg; + size_t ms_msg_size; +} ms_kbupd_enclave_ocall_panic_t; + +typedef struct ms_sgxsd_ocall_reply_t { + sgx_status_t ms_retval; + const sgxsd_msg_header_t* ms_reply_header; + const uint8_t* ms_reply_data; + size_t ms_reply_data_size; + sgxsd_msg_tag_t ms_msg_tag; +} ms_sgxsd_ocall_reply_t; + +typedef struct ms_sgx_oc_cpuidex_t { + int* ms_cpuinfo; + int ms_leaf; + int ms_subleaf; +} ms_sgx_oc_cpuidex_t; + +typedef struct ms_sgx_thread_wait_untrusted_event_ocall_t { + int ms_retval; + const void* ms_self; +} ms_sgx_thread_wait_untrusted_event_ocall_t; + +typedef struct ms_sgx_thread_set_untrusted_event_ocall_t { + int ms_retval; + const void* ms_waiter; +} ms_sgx_thread_set_untrusted_event_ocall_t; + +typedef struct ms_sgx_thread_setwait_untrusted_events_ocall_t { + int ms_retval; + const void* ms_waiter; + const void* ms_self; +} ms_sgx_thread_setwait_untrusted_events_ocall_t; + +typedef struct ms_sgx_thread_set_multiple_untrusted_events_ocall_t { + int ms_retval; + const void** ms_waiters; + size_t ms_total; +} ms_sgx_thread_set_multiple_untrusted_events_ocall_t; + +static sgx_status_t SGX_CDECL kbupd_enclave_kbupd_enclave_ocall_recv_enclave_msg(void* pms) +{ + ms_kbupd_enclave_ocall_recv_enclave_msg_t* ms = SGX_CAST(ms_kbupd_enclave_ocall_recv_enclave_msg_t*, pms); + kbupd_enclave_ocall_recv_enclave_msg(ms->ms_data, ms->ms_data_size); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_kbupd_enclave_ocall_alloc(void* pms) +{ + ms_kbupd_enclave_ocall_alloc_t* ms = SGX_CAST(ms_kbupd_enclave_ocall_alloc_t*, pms); + ms->ms_retval = kbupd_enclave_ocall_alloc(ms->ms_size); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_kbupd_enclave_ocall_panic(void* pms) +{ + ms_kbupd_enclave_ocall_panic_t* ms = SGX_CAST(ms_kbupd_enclave_ocall_panic_t*, pms); + kbupd_enclave_ocall_panic(ms->ms_msg, ms->ms_msg_size); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_sgxsd_ocall_reply(void* pms) +{ + ms_sgxsd_ocall_reply_t* ms = SGX_CAST(ms_sgxsd_ocall_reply_t*, pms); + ms->ms_retval = sgxsd_ocall_reply(ms->ms_reply_header, ms->ms_reply_data, ms->ms_reply_data_size, ms->ms_msg_tag); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_sgx_oc_cpuidex(void* pms) +{ + ms_sgx_oc_cpuidex_t* ms = SGX_CAST(ms_sgx_oc_cpuidex_t*, pms); + sgx_oc_cpuidex(ms->ms_cpuinfo, ms->ms_leaf, ms->ms_subleaf); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_sgx_thread_wait_untrusted_event_ocall(void* pms) +{ + ms_sgx_thread_wait_untrusted_event_ocall_t* ms = SGX_CAST(ms_sgx_thread_wait_untrusted_event_ocall_t*, pms); + ms->ms_retval = sgx_thread_wait_untrusted_event_ocall(ms->ms_self); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_sgx_thread_set_untrusted_event_ocall(void* pms) +{ + ms_sgx_thread_set_untrusted_event_ocall_t* ms = SGX_CAST(ms_sgx_thread_set_untrusted_event_ocall_t*, pms); + ms->ms_retval = sgx_thread_set_untrusted_event_ocall(ms->ms_waiter); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_sgx_thread_setwait_untrusted_events_ocall(void* pms) +{ + ms_sgx_thread_setwait_untrusted_events_ocall_t* ms = SGX_CAST(ms_sgx_thread_setwait_untrusted_events_ocall_t*, pms); + ms->ms_retval = sgx_thread_setwait_untrusted_events_ocall(ms->ms_waiter, ms->ms_self); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_sgx_thread_set_multiple_untrusted_events_ocall(void* pms) +{ + ms_sgx_thread_set_multiple_untrusted_events_ocall_t* ms = SGX_CAST(ms_sgx_thread_set_multiple_untrusted_events_ocall_t*, pms); + ms->ms_retval = sgx_thread_set_multiple_untrusted_events_ocall(ms->ms_waiters, ms->ms_total); + + return SGX_SUCCESS; +} + +static const struct { + size_t nr_ocall; + void * table[9]; +} ocall_table_kbupd_enclave = { + 9, + { + (void*)kbupd_enclave_kbupd_enclave_ocall_recv_enclave_msg, + (void*)kbupd_enclave_kbupd_enclave_ocall_alloc, + (void*)kbupd_enclave_kbupd_enclave_ocall_panic, + (void*)kbupd_enclave_sgxsd_ocall_reply, + (void*)kbupd_enclave_sgx_oc_cpuidex, + (void*)kbupd_enclave_sgx_thread_wait_untrusted_event_ocall, + (void*)kbupd_enclave_sgx_thread_set_untrusted_event_ocall, + (void*)kbupd_enclave_sgx_thread_setwait_untrusted_events_ocall, + (void*)kbupd_enclave_sgx_thread_set_multiple_untrusted_events_ocall, + } +}; +sgx_status_t kbupd_enclave_recv_untrusted_msg(sgx_enclave_id_t eid, const uint8_t* data, size_t data_size) +{ + sgx_status_t status; + ms_kbupd_enclave_recv_untrusted_msg_t ms; + ms.ms_data = data; + ms.ms_data_size = data_size; + status = sgx_ecall(eid, 0, &ocall_table_kbupd_enclave, &ms); + return status; +} + +sgx_status_t sgxsd_enclave_node_init(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_node_init_args_t* p_args) +{ + sgx_status_t status; + ms_sgxsd_enclave_node_init_t ms; + ms.ms_p_args = p_args; + status = sgx_ecall(eid, 1, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgxsd_enclave_get_next_report(sgx_enclave_id_t eid, sgx_status_t* retval, sgx_target_info_t qe_target_info, sgx_report_t* p_report) +{ + sgx_status_t status; + ms_sgxsd_enclave_get_next_report_t ms; + ms.ms_qe_target_info = qe_target_info; + ms.ms_p_report = p_report; + status = sgx_ecall(eid, 2, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgxsd_enclave_set_current_quote(sgx_enclave_id_t eid, sgx_status_t* retval) +{ + sgx_status_t status; + ms_sgxsd_enclave_set_current_quote_t ms; + status = sgx_ecall(eid, 3, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgxsd_enclave_negotiate_request(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_request_negotiation_request_t* p_request, sgxsd_request_negotiation_response_t* p_response) +{ + sgx_status_t status; + ms_sgxsd_enclave_negotiate_request_t ms; + ms.ms_p_request = p_request; + ms.ms_p_response = p_response; + status = sgx_ecall(eid, 4, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgxsd_enclave_server_start(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_server_init_args_t* p_args, sgxsd_server_state_handle_t state_handle) +{ + sgx_status_t status; + ms_sgxsd_enclave_server_start_t ms; + ms.ms_p_args = p_args; + ms.ms_state_handle = state_handle; + status = sgx_ecall(eid, 5, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgxsd_enclave_server_call(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_server_handle_call_args_t* p_args, const sgxsd_msg_header_t* msg_header, uint8_t* msg_data, size_t msg_size, sgxsd_msg_tag_t msg_tag, sgxsd_server_state_handle_t state_handle) +{ + sgx_status_t status; + ms_sgxsd_enclave_server_call_t ms; + ms.ms_p_args = p_args; + ms.ms_msg_header = msg_header; + ms.ms_msg_data = msg_data; + ms.ms_msg_size = msg_size; + ms.ms_msg_tag = msg_tag; + ms.ms_state_handle = state_handle; + status = sgx_ecall(eid, 6, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgxsd_enclave_server_stop(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_server_terminate_args_t* p_args, sgxsd_server_state_handle_t state_handle) +{ + sgx_status_t status; + ms_sgxsd_enclave_server_stop_t ms; + ms.ms_p_args = p_args; + ms.ms_state_handle = state_handle; + status = sgx_ecall(eid, 7, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + diff --git a/enclave/include/kbupd_enclave_u.h b/enclave/include/kbupd_enclave_u.h new file mode 100644 index 0000000..bc1191c --- /dev/null +++ b/enclave/include/kbupd_enclave_u.h @@ -0,0 +1,74 @@ +#ifndef KBUPD_ENCLAVE_U_H__ +#define KBUPD_ENCLAVE_U_H__ + +#include +#include +#include +#include +#include "sgx_edger8r.h" /* for sgx_status_t etc. */ + +#include "kbupd_sgxsd_callbacks.h" +#include "stdbool.h" +#include "sgx_quote.h" +#include "sgx_report.h" +#include "sgxsd.h" + +#include /* for size_t */ + +#define SGX_CAST(type, item) ((type)(item)) + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef KBUPD_ENCLAVE_OCALL_RECV_ENCLAVE_MSG_DEFINED__ +#define KBUPD_ENCLAVE_OCALL_RECV_ENCLAVE_MSG_DEFINED__ +void SGX_UBRIDGE(SGX_NOCONVENTION, kbupd_enclave_ocall_recv_enclave_msg, (const uint8_t* data, size_t data_size)); +#endif +#ifndef KBUPD_ENCLAVE_OCALL_ALLOC_DEFINED__ +#define KBUPD_ENCLAVE_OCALL_ALLOC_DEFINED__ +void* SGX_UBRIDGE(SGX_NOCONVENTION, kbupd_enclave_ocall_alloc, (size_t* size)); +#endif +#ifndef KBUPD_ENCLAVE_OCALL_PANIC_DEFINED__ +#define KBUPD_ENCLAVE_OCALL_PANIC_DEFINED__ +void SGX_UBRIDGE(SGX_NOCONVENTION, kbupd_enclave_ocall_panic, (const uint8_t* msg, size_t msg_size)); +#endif +#ifndef SGXSD_OCALL_REPLY_DEFINED__ +#define SGXSD_OCALL_REPLY_DEFINED__ +sgx_status_t SGX_UBRIDGE(SGX_NOCONVENTION, sgxsd_ocall_reply, (const sgxsd_msg_header_t* reply_header, const uint8_t* reply_data, size_t reply_data_size, sgxsd_msg_tag_t msg_tag)); +#endif +#ifndef SGX_OC_CPUIDEX_DEFINED__ +#define SGX_OC_CPUIDEX_DEFINED__ +void SGX_UBRIDGE(SGX_CDECL, sgx_oc_cpuidex, (int cpuinfo[4], int leaf, int subleaf)); +#endif +#ifndef SGX_THREAD_WAIT_UNTRUSTED_EVENT_OCALL_DEFINED__ +#define SGX_THREAD_WAIT_UNTRUSTED_EVENT_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, sgx_thread_wait_untrusted_event_ocall, (const void* self)); +#endif +#ifndef SGX_THREAD_SET_UNTRUSTED_EVENT_OCALL_DEFINED__ +#define SGX_THREAD_SET_UNTRUSTED_EVENT_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, sgx_thread_set_untrusted_event_ocall, (const void* waiter)); +#endif +#ifndef SGX_THREAD_SETWAIT_UNTRUSTED_EVENTS_OCALL_DEFINED__ +#define SGX_THREAD_SETWAIT_UNTRUSTED_EVENTS_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, sgx_thread_setwait_untrusted_events_ocall, (const void* waiter, const void* self)); +#endif +#ifndef SGX_THREAD_SET_MULTIPLE_UNTRUSTED_EVENTS_OCALL_DEFINED__ +#define SGX_THREAD_SET_MULTIPLE_UNTRUSTED_EVENTS_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, sgx_thread_set_multiple_untrusted_events_ocall, (const void** waiters, size_t total)); +#endif + +sgx_status_t kbupd_enclave_recv_untrusted_msg(sgx_enclave_id_t eid, const uint8_t* data, size_t data_size); +sgx_status_t sgxsd_enclave_node_init(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_node_init_args_t* p_args); +sgx_status_t sgxsd_enclave_get_next_report(sgx_enclave_id_t eid, sgx_status_t* retval, sgx_target_info_t qe_target_info, sgx_report_t* p_report); +sgx_status_t sgxsd_enclave_set_current_quote(sgx_enclave_id_t eid, sgx_status_t* retval); +sgx_status_t sgxsd_enclave_negotiate_request(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_request_negotiation_request_t* p_request, sgxsd_request_negotiation_response_t* p_response); +sgx_status_t sgxsd_enclave_server_start(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_server_init_args_t* p_args, sgxsd_server_state_handle_t state_handle); +sgx_status_t sgxsd_enclave_server_call(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_server_handle_call_args_t* p_args, const sgxsd_msg_header_t* msg_header, uint8_t* msg_data, size_t msg_size, sgxsd_msg_tag_t msg_tag, sgxsd_server_state_handle_t state_handle); +sgx_status_t sgxsd_enclave_server_stop(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_server_terminate_args_t* p_args, sgxsd_server_state_handle_t state_handle); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/enclave/include/kbupd_sgxsd_callbacks.h b/enclave/include/kbupd_sgxsd_callbacks.h new file mode 100644 index 0000000..5fcd674 --- /dev/null +++ b/enclave/include/kbupd_sgxsd_callbacks.h @@ -0,0 +1,31 @@ +#ifndef _KBUPD_SGXSD_CALLBACKS_H +#define _KBUPD_SGXSD_CALLBACKS_H + +#include + +typedef enum kbupd_request_type { + KBUPD_REQUEST_TYPE_ANY = 0, + KBUPD_REQUEST_TYPE_BACKUP = 1, + KBUPD_REQUEST_TYPE_RESTORE = 2, + KBUPD_REQUEST_TYPE_DELETE = 3, +} kbupd_request_type_t; + +typedef struct sgxsd_server_init_args { +} sgxsd_server_init_args_t; +_Static_assert(sizeof(sgxsd_server_init_args_t) == 0, "Enclave ABI compatibility"); + +typedef struct sgxsd_server_handle_call_args { + uint8_t backup_id[32]; + uint32_t request_type; +} sgxsd_server_handle_call_args_t; +_Static_assert(sizeof(sgxsd_server_handle_call_args_t) == 36, "Enclave ABI compatibility"); + +typedef struct sgxsd_server_terminate_args { +} sgxsd_server_terminate_args_t; +_Static_assert(sizeof(sgxsd_server_terminate_args_t) == 0, "Enclave ABI compatibility"); + +typedef struct sgxsd_ra_get_quote_args { + const void *args; +} sgxsd_ra_get_quote_args_t; + +#endif diff --git a/enclave/include/sgxsd-enclave.h b/enclave/include/sgxsd-enclave.h new file mode 100644 index 0000000..ec8b125 --- /dev/null +++ b/enclave/include/sgxsd-enclave.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @author Jeff Griffin + */ +#ifndef _SGXSD_ENCLAVE_H +#define _SGXSD_ENCLAVE_H + +#include "sgx_error.h" + +#include "sgxsd.h" + +typedef struct sgxsd_msg_buf { + uint8_t *data; + uint32_t size; +} sgxsd_msg_buf_t; + +typedef struct sgxsd_msg_from { + bool valid; + sgxsd_msg_tag_t tag; + sgxsd_aes_gcm_key_t server_key; +} sgxsd_msg_from_t; + +// +// callbacks +// + +// the incomplete type sgxsd_server_state doesn't necessarily need to be defined +typedef struct sgxsd_server_state sgxsd_server_state_t; + +/* the incomplete types sgxsd_server_{init,handle_call,terminate}_args must be defined and included before sgxsd APIs + in the .edl file */ +typedef struct sgxsd_server_init_args sgxsd_server_init_args_t; +typedef struct sgxsd_server_handle_call_args sgxsd_server_handle_call_args_t; +typedef struct sgxsd_server_terminate_args sgxsd_server_terminate_args_t; + +// the callbacks sgxsd_enclave_server_{init,handle_call,terminate} handle sgxsd_enclave_server_{start,call,stop} calls +sgx_status_t sgxsd_enclave_server_init(const sgxsd_server_init_args_t *p_args, sgxsd_server_state_t **pp_state); +sgx_status_t sgxsd_enclave_server_handle_call(const sgxsd_server_handle_call_args_t *p_args, sgxsd_msg_buf_t msg, sgxsd_msg_from_t from, sgxsd_server_state_t **pp_state); +sgx_status_t sgxsd_enclave_server_terminate(const sgxsd_server_terminate_args_t *p_args, sgxsd_server_state_t *p_state); + +// +// public api +// + +// sgxsd_enclave_server_reply sends a reply to the message corresponding to the given sgxsd_msg_from from handle_call +sgx_status_t sgxsd_enclave_server_reply(sgxsd_msg_buf_t reply_buf, sgxsd_msg_from_t *p_from); + +sgx_status_t sgxsd_enclave_server_noreply(sgxsd_msg_from_t *p_from); + +// +// public utility apis +// + +sgx_status_t sgxsd_aes_gcm_encrypt(const sgxsd_aes_gcm_key_t *p_key, + const void *p_src, uint32_t src_len, void *p_dst, + const sgxsd_aes_gcm_iv_t *p_iv, + const void *p_aad, uint32_t aad_len, + sgxsd_aes_gcm_mac_t *p_out_mac); + +sgx_status_t sgxsd_aes_gcm_decrypt(const sgxsd_aes_gcm_key_t *p_key, + const void *p_src, uint32_t src_len, void *p_dst, + const sgxsd_aes_gcm_iv_t *p_iv, + const void *p_aad, uint32_t aad_len, + const sgxsd_aes_gcm_mac_t *p_in_mac); + +typedef struct sgxsd_rand_buf { + uint8_t x[SGXSD_CURVE25519_KEY_SIZE]; +} sgxsd_rand_buf_t, sgxsd_curve25519_private_key_t; + +sgx_status_t sgxsd_enclave_read_rand(sgxsd_rand_buf_t *p_privkey); + +// +// internal definitions +// + +typedef struct sgxsd_curve25519_key_pair { + sgxsd_curve25519_private_key_t privkey; + sgxsd_curve25519_public_key_t pubkey; +} sgxsd_curve25519_key_pair_t; + +#endif diff --git a/enclave/include/sgxsd.edl b/enclave/include/sgxsd.edl new file mode 100644 index 0000000..e628b3e --- /dev/null +++ b/enclave/include/sgxsd.edl @@ -0,0 +1,36 @@ +enclave { + include "stdbool.h" + include "sgx_quote.h" + include "sgx_report.h" + include "sgxsd.h" + + trusted { + public sgx_status_t sgxsd_enclave_node_init([in] const sgxsd_node_init_args_t *p_args); + + public sgx_status_t sgxsd_enclave_get_next_report + (sgx_target_info_t qe_target_info, [out] sgx_report_t *p_report); + public sgx_status_t sgxsd_enclave_set_current_quote(); + + public sgx_status_t sgxsd_enclave_negotiate_request + ([in] const sgxsd_request_negotiation_request_t *p_request, + [out] sgxsd_request_negotiation_response_t *p_response); + + public sgx_status_t sgxsd_enclave_server_start + ([in] const sgxsd_server_init_args_t *p_args, + sgxsd_server_state_handle_t state_handle); + public sgx_status_t sgxsd_enclave_server_call + ([in] const sgxsd_server_handle_call_args_t *p_args, + [in] const sgxsd_msg_header_t *msg_header, + [in, size=msg_size] uint8_t *msg_data, size_t msg_size, + sgxsd_msg_tag_t msg_tag, sgxsd_server_state_handle_t state_handle); + public sgx_status_t sgxsd_enclave_server_stop + ([in] const sgxsd_server_terminate_args_t *p_args, + sgxsd_server_state_handle_t state_handle); + }; + untrusted { + sgx_status_t sgxsd_ocall_reply + ([in] const sgxsd_msg_header_t *reply_header, + [in, size=reply_data_size] const uint8_t *reply_data, size_t reply_data_size, + sgxsd_msg_tag_t msg_tag); + }; +}; diff --git a/enclave/include/sgxsd.h b/enclave/include/sgxsd.h new file mode 100644 index 0000000..536ee74 --- /dev/null +++ b/enclave/include/sgxsd.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2017 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @author Jeff Griffin + */ +#ifndef _SGXSD_H +#define _SGXSD_H + +#include +#include +#include + +#include "sgx_urts.h" +#include "sgx_quote.h" + +// +// types +// + +#define SGXSD_AES_GCM_IV_SIZE 12 +#define SGXSD_AES_GCM_MAC_SIZE 16 +#define SGXSD_AES_GCM_KEY_SIZE 32 +#define SGXSD_CURVE25519_KEY_SIZE 32 +#define SGXSD_SHA256_HASH_SIZE 32 + +typedef struct sgxsd_aes_gcm_mac { + uint8_t data[SGXSD_AES_GCM_MAC_SIZE]; +} sgxsd_aes_gcm_mac_t; +_Static_assert(sizeof(sgxsd_aes_gcm_mac_t) == SGXSD_AES_GCM_MAC_SIZE, "Enclave ABI compatibility"); + +typedef struct sgxsd_aes_gcm_iv { + uint8_t data[SGXSD_AES_GCM_IV_SIZE]; +} sgxsd_aes_gcm_iv_t; +_Static_assert(sizeof(sgxsd_aes_gcm_iv_t) == SGXSD_AES_GCM_IV_SIZE, "Enclave ABI compatibility"); + +typedef struct sgxsd_aes_gcm_key { + uint8_t data[SGXSD_AES_GCM_KEY_SIZE]; +} sgxsd_aes_gcm_key_t; +_Static_assert(sizeof(sgxsd_aes_gcm_key_t) == SGXSD_AES_GCM_KEY_SIZE, "Enclave ABI compatibility"); + +typedef struct sgxsd_curve25519_public_key { + uint8_t x[SGXSD_CURVE25519_KEY_SIZE]; +} sgxsd_curve25519_public_key_t; +_Static_assert(sizeof(sgxsd_curve25519_public_key_t) == SGXSD_CURVE25519_KEY_SIZE, "Enclave ABI compatibility"); + +typedef struct sgxsd_request_negotiation_request { + sgxsd_curve25519_public_key_t client_pubkey; +} sgxsd_request_negotiation_request_t; +_Static_assert(sizeof(sgxsd_request_negotiation_request_t) == sizeof(sgxsd_curve25519_public_key_t), "Enclave ABI compatibility"); + +typedef struct sgxsd_pending_request_id { + uint8_t data[sizeof(uint64_t)]; + sgxsd_aes_gcm_iv_t iv; + sgxsd_aes_gcm_mac_t mac; +} sgxsd_pending_request_id_t; +_Static_assert(sizeof(sgxsd_pending_request_id_t) == sizeof(uint64_t) + sizeof(sgxsd_aes_gcm_iv_t) + sizeof(sgxsd_aes_gcm_mac_t), "Enclave ABI compatibility"); + +typedef struct sgxsd_request_negotiation_response { + sgxsd_curve25519_public_key_t server_static_pubkey; + sgxsd_curve25519_public_key_t server_ephemeral_pubkey; + struct { + uint8_t data[sizeof(sgxsd_pending_request_id_t)]; + sgxsd_aes_gcm_iv_t iv; + sgxsd_aes_gcm_mac_t mac; + } encrypted_pending_request_id; +} sgxsd_request_negotiation_response_t; +_Static_assert(sizeof(sgxsd_request_negotiation_response_t) == sizeof(sgxsd_curve25519_public_key_t) * 2 + sizeof(sgxsd_pending_request_id_t) + sizeof(sgxsd_aes_gcm_iv_t) + sizeof(sgxsd_aes_gcm_mac_t), "Enclave ABI compatibility"); + +typedef struct sgxsd_msg_tag { + union { + void *p_tag; + uint64_t tag; + }; +} sgxsd_msg_tag_t; +_Static_assert(sizeof(sgxsd_msg_tag_t) == sizeof(uint64_t), "Enclave ABI compatibility"); + +typedef struct sgxsd_msg_header { + sgxsd_aes_gcm_iv_t iv; + sgxsd_aes_gcm_mac_t mac; + sgxsd_pending_request_id_t pending_request_id; +} sgxsd_msg_header_t; +_Static_assert(sizeof(sgxsd_msg_header_t) == sizeof(sgxsd_aes_gcm_iv_t) + sizeof(sgxsd_aes_gcm_mac_t) + sizeof(sgxsd_pending_request_id_t), "Enclave ABI compatibility"); + +typedef struct sgxsd_node_init_args { + uint8_t pending_requests_table_order; +} sgxsd_node_init_args_t; + +typedef uint64_t sgxsd_server_state_handle_t; + +// +// public api (untrusted) +// + +#define sgxsd_status_ok() (sgxsd_status_t) { .ok = true, .name = "ok", .code = 0 } +#define sgxsd_status_error(Name) (sgxsd_status_t) { .ok = false, .name = Name, .code = 0 } +#define sgxsd_status_error_code(Name, SgxStatus) (sgxsd_status_t) { .ok = false, .name = Name, .code = SgxStatus } +typedef struct sgxsd_status { + bool ok; + const char *name; + int64_t code; +} sgxsd_status_t; + +typedef struct sgxsd_enclave { + sgx_enclave_id_t id; + union { + sgx_epid_group_id_t gid; + uint32_t gid32; + }; + sgx_launch_token_t launch_token; +} sgxsd_enclave_t; + +typedef sgxsd_status_t (*sgxsd_start_callback_t)(sgxsd_enclave_t, va_list); +sgxsd_status_t sgxsd_start(const char *enclave_path, bool debug, const sgx_launch_token_t *p_launch_token, const sgxsd_node_init_args_t *p_node_init_args, sgxsd_start_callback_t p_callback, ...); +sgxsd_status_t sgxsd_get_next_quote(sgx_enclave_id_t enclave_id, sgx_spid_t spid, const uint8_t *p_sig_rl, uint32_t sig_rl_size, sgx_quote_t *p_quote, uint32_t quote_size); + +// +// error codes +// + +typedef enum sgxsd_status_code { + SGXSD_ERROR_PENDING_REQUEST_NOT_FOUND = SGX_MK_ERROR(0x10001), +} sgxsd_status_code_t; + +#endif diff --git a/enclave/include/test_memset_s.h b/enclave/include/test_memset_s.h new file mode 100644 index 0000000..a9ff00c --- /dev/null +++ b/enclave/include/test_memset_s.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#ifndef TEST_MEMSET_S_H__ +#define TEST_MEMSET_S_H__ + +#include +#include +#include +#include + +static void *(* const volatile __memset_vp)(void *, int, size_t) = memset; +static inline +int memset_s(void *s, size_t smax, int c, size_t n) { + int err = 0; + + if (s == NULL) { + errno = err = EINVAL; + return err; + } + if (smax > SIZE_MAX) { + errno = err = E2BIG; + return err; + } + if (n > SIZE_MAX) { + err = E2BIG; + n = smax; + } + if (n > smax) { + err = EOVERFLOW; + n = smax; + } + + (*__memset_vp)(s, c, n); + + if (err == 0) { + return 0; + } else { + errno = err; + return err; + } +} + +#endif diff --git a/enclave/kbupd_enclave/.cargo/config b/enclave/kbupd_enclave/.cargo/config new file mode 100644 index 0000000..fe9c8d6 --- /dev/null +++ b/enclave/kbupd_enclave/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/target" diff --git a/enclave/kbupd_enclave/Cargo.toml b/enclave/kbupd_enclave/Cargo.toml new file mode 100644 index 0000000..d2ce805 --- /dev/null +++ b/enclave/kbupd_enclave/Cargo.toml @@ -0,0 +1,44 @@ +[package] +authors = ["Open Whisper Systems"] +name = "kbupd_enclave" +version = "0.1.0" +license = "AGPL-3.0-or-later" +edition = "2018" + +[features] +default = [] +insecure = [] +debug = ["insecure"] +trace = ["debug", "insecure"] +test = ["sgx_ffi/test", "sgxsd_ffi/test"] + +[dependencies] +base64 = { rev = "07b1d6b713cc2bd7d107185bd0d14bf06cddfb48", git = "https://github.com/marshallpierce/rust-base64.git", default-features = false, features = ["alloc"] } +bytes = { rev = "ebe96021b0eaf52be1fedd0a925f4384275c9cc4", git = "https://github.com/tokio-rs/bytes.git", default-features = false, features = [] } +chrono = { rev = "de22e82a1b00b8f015a1d736e497c3177e8e8c9f", git = "https://github.com/geogriff-signal/chrono.git", default-features = false, features = [] } +hashbrown = { version = "0.6", default-features = false, features = [] } +intrusive-collections = { rev = "3c14ea97598616a2beaa97cd5fbbb27ba574fe77", git = "https://github.com/geogriff-signal/intrusive-rs.git" } +lazy_static = { version = "1.4", features = ["spin_no_std"] } +libc = { version = "0.2", default-features = false, features = [] } +no-std-compat = { version = "0.2", features = ["alloc"] } +num-traits = { version = "0.2", default-features = false, features = [] } +prost = { rev = "907f7d6e714d0b449d75269d532561c71d3ed250", git = "https://github.com/geogriff-signal/prost.git", default-features = false, features = ["prost-derive"] } +rand_core = { version = "0.5", default-features = false, features = [] } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_json = { rev = "d79b0c67f62e168d4872bb8694377ffd97b8949f", git = "https://github.com/geogriff-signal/serde_json.git", default-features = false, features = [] } +sgx_ffi = { path = "../sgx_ffi" } +sgxsd_ffi = { path = "../sgxsd_ffi" } +spin = "0.5" +snow = { rev = "d8d00a37c8e39b2557d23a26cc4f722595b4f2d9", git = "https://github.com/geogriff-signal/snow.git", default-features = false, features = [] } +webpki = { rev = "32ab63c46edfbfe9c1fe68008ba8e247eb387ca3", git = "https://github.com/geogriff-signal/webpki.git", default-features = false, features = [] } + +[dev-dependencies] +lazy_static = "1.4" +mockers = "0.21" +mockers_derive = "0.21" +rand = { version = "0.7", default-features = false, features = [] } +rand_chacha = { version = "0.2", default-features = false, features = [] } +test_ffi = { path = "../test_ffi" } + +[lib] +crate-type = ["staticlib"] diff --git a/enclave/kbupd_enclave/lsan-ignore-test.txt b/enclave/kbupd_enclave/lsan-ignore-test.txt new file mode 100644 index 0000000..7fa50f3 --- /dev/null +++ b/enclave/kbupd_enclave/lsan-ignore-test.txt @@ -0,0 +1 @@ +leak:backtrace_alloc diff --git a/enclave/kbupd_enclave/src/allocator.rs b/enclave/kbupd_enclave/src/allocator.rs new file mode 100644 index 0000000..09b8d27 --- /dev/null +++ b/enclave/kbupd_enclave/src/allocator.rs @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use alloc::alloc::*; +use core::cmp; +use core::ptr; + +#[cfg(target_arch = "x86_64")] +const MIN_ALIGN: usize = 8; + +pub struct System; +unsafe impl GlobalAlloc for System { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() { + libc::malloc(layout.size()) as *mut u8 + } else { + libc::memalign(layout.align(), layout.size()) as *mut u8 + } + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() { + libc::calloc(layout.size(), 1) as *mut u8 + } else { + let ptr = self.alloc(layout.clone()); + if !ptr.is_null() { + ptr::write_bytes(ptr, 0, layout.size()); + } + ptr + } + } + + #[inline] + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + libc::free(ptr as *mut libc::c_void) + } + + #[inline] + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + if layout.align() <= MIN_ALIGN && layout.align() <= new_size { + libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8 + } else { + realloc_fallback(self, ptr, layout, new_size) + } + } +} + +unsafe fn realloc_fallback(alloc: &System, ptr: *mut u8, old_layout: Layout, new_size: usize) -> *mut u8 { + let new_layout = Layout::from_size_align_unchecked(new_size, old_layout.align()); + + let new_ptr = GlobalAlloc::alloc(alloc, new_layout); + if !new_ptr.is_null() { + let size = cmp::min(old_layout.size(), new_size); + ptr::copy_nonoverlapping(ptr, new_ptr, size); + GlobalAlloc::dealloc(alloc, ptr, old_layout); + } + new_ptr +} + +#[alloc_error_handler] +pub fn handle_alloc_error(layout: Layout) -> ! { + let status = sgx_ffi::util::MemoryStatus::collect(); + panic!("out of memory allocating {} bytes with {} used of {} bytes in {} chunks", + layout.size(), status.used_bytes, status.footprint_bytes, status.free_chunks); +} diff --git a/enclave/kbupd_enclave/src/ffi/bindgen_wrapper.h b/enclave/kbupd_enclave/src/ffi/bindgen_wrapper.h new file mode 100644 index 0000000..f68d50f --- /dev/null +++ b/enclave/kbupd_enclave/src/ffi/bindgen_wrapper.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include "kbupd_sgxsd_callbacks.h" +#include "kbupd_enclave_t.h" diff --git a/enclave/kbupd_enclave/src/ffi/bindgen_wrapper.rs b/enclave/kbupd_enclave/src/ffi/bindgen_wrapper.rs new file mode 100644 index 0000000..fdb00db --- /dev/null +++ b/enclave/kbupd_enclave/src/ffi/bindgen_wrapper.rs @@ -0,0 +1,2844 @@ +/* automatically generated by rust-bindgen */ + +#[repr(C)] +#[derive(Default)] +pub struct __IncompleteArrayField(::core::marker::PhantomData, [T; 0]); +impl __IncompleteArrayField { + #[inline] + pub const fn new() -> Self { + __IncompleteArrayField(::core::marker::PhantomData, []) + } + #[inline] + pub unsafe fn as_ptr(&self) -> *const T { + ::core::mem::transmute(self) + } + #[inline] + pub unsafe fn as_mut_ptr(&mut self) -> *mut T { + ::core::mem::transmute(self) + } + #[inline] + pub unsafe fn as_slice(&self, len: usize) -> &[T] { + ::core::slice::from_raw_parts(self.as_ptr(), len) + } + #[inline] + pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { + ::core::slice::from_raw_parts_mut(self.as_mut_ptr(), len) + } +} +impl ::core::fmt::Debug for __IncompleteArrayField { + fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + fmt.write_str("__IncompleteArrayField") + } +} +impl ::core::clone::Clone for __IncompleteArrayField { + #[inline] + fn clone(&self) -> Self { + Self::new() + } +} +pub const INT8_MIN: i32 = -128; +pub const INT16_MIN: i32 = -32768; +pub const INT32_MIN: i32 = -2147483648; +pub const INT64_MIN: i64 = -9223372036854775808; +pub const INT8_MAX: u32 = 127; +pub const INT16_MAX: u32 = 32767; +pub const INT32_MAX: u32 = 2147483647; +pub const INT64_MAX: u64 = 9223372036854775807; +pub const UINT8_MAX: u32 = 255; +pub const UINT16_MAX: u32 = 65535; +pub const UINT32_MAX: u32 = 4294967295; +pub const UINT64_MAX: i32 = -1; +pub const INT_LEAST8_MIN: i32 = -128; +pub const INT_LEAST16_MIN: i32 = -32768; +pub const INT_LEAST32_MIN: i32 = -2147483648; +pub const INT_LEAST64_MIN: i64 = -9223372036854775808; +pub const INT_LEAST8_MAX: u32 = 127; +pub const INT_LEAST16_MAX: u32 = 32767; +pub const INT_LEAST32_MAX: u32 = 2147483647; +pub const INT_LEAST64_MAX: u64 = 9223372036854775807; +pub const UINT_LEAST8_MAX: u32 = 255; +pub const UINT_LEAST16_MAX: u32 = 65535; +pub const UINT_LEAST32_MAX: u32 = 4294967295; +pub const UINT_LEAST64_MAX: i32 = -1; +pub const INT_FAST8_MIN: i32 = -128; +pub const INT_FAST16_MIN: i32 = -32768; +pub const INT_FAST32_MIN: i32 = -2147483648; +pub const INT_FAST64_MIN: i64 = -9223372036854775808; +pub const INT_FAST8_MAX: u32 = 127; +pub const INT_FAST16_MAX: u64 = 9223372036854775807; +pub const INT_FAST32_MAX: u64 = 9223372036854775807; +pub const INT_FAST64_MAX: u64 = 9223372036854775807; +pub const UINT_FAST8_MAX: u32 = 255; +pub const UINT_FAST16_MAX: i32 = -1; +pub const UINT_FAST32_MAX: i32 = -1; +pub const UINT_FAST64_MAX: i32 = -1; +pub const INTPTR_MIN: i64 = -9223372036854775808; +pub const INTPTR_MAX: u64 = 9223372036854775807; +pub const UINTPTR_MAX: i32 = -1; +pub const INTMAX_MIN: i64 = -9223372036854775808; +pub const INTMAX_MAX: u64 = 9223372036854775807; +pub const UINTMAX_MAX: i32 = -1; +pub const PTRDIFF_MIN: i64 = -9223372036854775808; +pub const PTRDIFF_MAX: u64 = 9223372036854775807; +pub const SIZE_MAX: i32 = -1; +pub const WINT_MIN: u32 = 0; +pub const WINT_MAX: u32 = 4294967295; +pub const false_: u32 = 0; +pub const true_: u32 = 1; +pub const __bool_true_false_are_defined: u32 = 1; +pub const SGX_FLAGS_INITTED: u32 = 1; +pub const SGX_FLAGS_DEBUG: u32 = 2; +pub const SGX_FLAGS_MODE64BIT: u32 = 4; +pub const SGX_FLAGS_PROVISION_KEY: u32 = 16; +pub const SGX_FLAGS_EINITTOKEN_KEY: u32 = 32; +pub const SGX_FLAGS_KSS: u32 = 128; +pub const SGX_XFRM_LEGACY: u32 = 3; +pub const SGX_XFRM_AVX: u32 = 6; +pub const SGX_XFRM_AVX512: u32 = 230; +pub const SGX_XFRM_MPX: u32 = 24; +pub const SGX_XFRM_RESERVED: i32 = -232; +pub const SGX_KEYSELECT_EINITTOKEN: u32 = 0; +pub const SGX_KEYSELECT_PROVISION: u32 = 1; +pub const SGX_KEYSELECT_PROVISION_SEAL: u32 = 2; +pub const SGX_KEYSELECT_REPORT: u32 = 3; +pub const SGX_KEYSELECT_SEAL: u32 = 4; +pub const SGX_KEYPOLICY_MRENCLAVE: u32 = 1; +pub const SGX_KEYPOLICY_MRSIGNER: u32 = 2; +pub const SGX_KEYPOLICY_NOISVPRODID: u32 = 4; +pub const SGX_KEYPOLICY_CONFIGID: u32 = 8; +pub const SGX_KEYPOLICY_ISVFAMILYID: u32 = 16; +pub const SGX_KEYPOLICY_ISVEXTPRODID: u32 = 32; +pub const SGX_KEYID_SIZE: u32 = 32; +pub const SGX_CPUSVN_SIZE: u32 = 16; +pub const SGX_CONFIGID_SIZE: u32 = 64; +pub const SGX_KEY_REQUEST_RESERVED2_BYTES: u32 = 434; +pub const SGX_HASH_SIZE: u32 = 32; +pub const SGX_MAC_SIZE: u32 = 16; +pub const SGX_REPORT_DATA_SIZE: u32 = 64; +pub const SGX_ISVEXT_PROD_ID_SIZE: u32 = 16; +pub const SGX_ISV_FAMILY_ID_SIZE: u32 = 16; +pub const SGX_TARGET_INFO_RESERVED1_BYTES: u32 = 2; +pub const SGX_TARGET_INFO_RESERVED2_BYTES: u32 = 8; +pub const SGX_TARGET_INFO_RESERVED3_BYTES: u32 = 384; +pub const SGX_REPORT_BODY_RESERVED1_BYTES: u32 = 12; +pub const SGX_REPORT_BODY_RESERVED2_BYTES: u32 = 32; +pub const SGX_REPORT_BODY_RESERVED3_BYTES: u32 = 32; +pub const SGX_REPORT_BODY_RESERVED4_BYTES: u32 = 42; +pub const SGX_PLATFORM_INFO_SIZE: u32 = 101; +pub const EXIT_FAILURE: u32 = 1; +pub const EXIT_SUCCESS: u32 = 0; +pub const RAND_MAX: u32 = 2147483647; +pub const MB_CUR_MAX: u32 = 1; +pub const MAX_EX_FEATURES_COUNT: u32 = 32; +pub const SGX_CREATE_ENCLAVE_EX_PCL_BIT_IDX: u32 = 0; +pub const SGX_CREATE_ENCLAVE_EX_PCL: u32 = 1; +pub const SGX_CREATE_ENCLAVE_EX_SWITCHLESS_BIT_IDX: u32 = 1; +pub const SGX_CREATE_ENCLAVE_EX_SWITCHLESS: u32 = 2; +pub const SGX_CREATE_ENCLAVE_EX_KSS_BIT_IDX: u32 = 2; +pub const SGX_CREATE_ENCLAVE_EX_KSS: u32 = 4; +pub const _SGX_LAST_EX_FEATURE_IDX_: u32 = 2; +pub const SGX_DEBUG_FLAG: u32 = 1; +pub const SGXSD_AES_GCM_IV_SIZE: u32 = 12; +pub const SGXSD_AES_GCM_MAC_SIZE: u32 = 16; +pub const SGXSD_AES_GCM_KEY_SIZE: u32 = 32; +pub const SGXSD_CURVE25519_KEY_SIZE: u32 = 32; +pub const SGXSD_SHA256_HASH_SIZE: u32 = 32; +pub type __int8_t = libc::c_schar; +pub type __uint8_t = libc::c_uchar; +pub type __int16_t = libc::c_short; +pub type __uint16_t = libc::c_ushort; +pub type __int32_t = libc::c_int; +pub type __uint32_t = libc::c_uint; +pub type __int64_t = libc::c_long; +pub type __uint64_t = libc::c_ulong; +pub type __int_least8_t = __int8_t; +pub type __uint_least8_t = __uint8_t; +pub type __int_least16_t = __int16_t; +pub type __uint_least16_t = __uint16_t; +pub type __int_least32_t = __int32_t; +pub type __uint_least32_t = __uint32_t; +pub type __int_least64_t = __int64_t; +pub type __uint_least64_t = __uint64_t; +pub type __int_fast8_t = __int8_t; +pub type __uint_fast8_t = __uint8_t; +pub type __int_fast16_t = libc::c_long; +pub type __uint_fast16_t = libc::c_ulong; +pub type __int_fast32_t = libc::c_long; +pub type __uint_fast32_t = libc::c_ulong; +pub type __int_fast64_t = libc::c_long; +pub type __uint_fast64_t = libc::c_ulong; +pub type __off_t = libc::c_long; +pub type __intptr_t = __int64_t; +pub type __uintptr_t = __uint64_t; +pub type __ptrdiff_t = __int64_t; +pub type __size_t = libc::c_ulong; +pub type __ssize_t = libc::c_long; +pub type __double_t = f64; +pub type __float_t = f32; +pub type __clock_t = libc::c_long; +pub type __time_t = libc::c_long; +pub type __va_list = __builtin_va_list; +pub type __wint_t = libc::c_uint; +pub type __wctype_t = libc::c_ulong; +pub type __wctrans_t = *mut libc::c_int; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct __mbstate_t { + pub __c: libc::c_int, + pub __v: __mbstate_t__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union __mbstate_t__bindgen_ty_1 { + pub __wc: __wint_t, + pub __wcb: [libc::c_char; 4usize], + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout___mbstate_t__bindgen_ty_1() { + assert_eq!( + ::core::mem::size_of::<__mbstate_t__bindgen_ty_1>(), + 4usize, + concat!("Size of: ", stringify!(__mbstate_t__bindgen_ty_1)) + ); + assert_eq!( + ::core::mem::align_of::<__mbstate_t__bindgen_ty_1>(), + 4usize, + concat!("Alignment of ", stringify!(__mbstate_t__bindgen_ty_1)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__mbstate_t__bindgen_ty_1>())).__wc as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t__bindgen_ty_1), + "::", + stringify!(__wc) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<__mbstate_t__bindgen_ty_1>())).__wcb as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t__bindgen_ty_1), + "::", + stringify!(__wcb) + ) + ); +} +impl Default for __mbstate_t__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout___mbstate_t() { + assert_eq!( + ::core::mem::size_of::<__mbstate_t>(), + 8usize, + concat!("Size of: ", stringify!(__mbstate_t)) + ); + assert_eq!( + ::core::mem::align_of::<__mbstate_t>(), + 4usize, + concat!("Alignment of ", stringify!(__mbstate_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__mbstate_t>())).__c as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t), + "::", + stringify!(__c) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__mbstate_t>())).__v as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t), + "::", + stringify!(__v) + ) + ); +} +impl Default for __mbstate_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type __intmax_t = __int64_t; +pub type __uintmax_t = __uint64_t; +pub type int_least8_t = __int_least8_t; +pub type uint_least8_t = __uint_least8_t; +pub type int_least16_t = __int_least16_t; +pub type uint_least16_t = __uint_least16_t; +pub type int_least32_t = __int_least32_t; +pub type uint_least32_t = __uint_least32_t; +pub type int_least64_t = __int_least64_t; +pub type uint_least64_t = __uint_least64_t; +pub type int_fast8_t = __int_fast8_t; +pub type uint_fast8_t = __uint_fast8_t; +pub type int_fast16_t = __int_fast16_t; +pub type uint_fast16_t = __uint_fast16_t; +pub type int_fast32_t = __int_fast32_t; +pub type uint_fast32_t = __uint_fast32_t; +pub type int_fast64_t = __int_fast64_t; +pub type uint_fast64_t = __uint_fast64_t; +pub type intmax_t = __intmax_t; +pub type uintmax_t = __uintmax_t; +pub const KBUPD_REQUEST_TYPE_ANY: kbupd_request_type = 0; +pub const KBUPD_REQUEST_TYPE_BACKUP: kbupd_request_type = 1; +pub const KBUPD_REQUEST_TYPE_RESTORE: kbupd_request_type = 2; +pub const KBUPD_REQUEST_TYPE_DELETE: kbupd_request_type = 3; +pub type kbupd_request_type = u32; +pub use self::kbupd_request_type as kbupd_request_type_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_server_init_args {} +#[test] +fn bindgen_test_layout_sgxsd_server_init_args() { + assert_eq!( + ::core::mem::size_of::(), + 0usize, + concat!("Size of: ", stringify!(sgxsd_server_init_args)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_server_init_args)) + ); +} +pub type sgxsd_server_init_args_t = sgxsd_server_init_args; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_server_handle_call_args { + pub backup_id: [u8; 32usize], + pub request_type: u32, +} +#[test] +fn bindgen_test_layout_sgxsd_server_handle_call_args() { + assert_eq!( + ::core::mem::size_of::(), + 36usize, + concat!("Size of: ", stringify!(sgxsd_server_handle_call_args)) + ); + assert_eq!( + ::core::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(sgxsd_server_handle_call_args)) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).backup_id as *const _ + as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_server_handle_call_args), + "::", + stringify!(backup_id) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).request_type as *const _ + as usize + }, + 32usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_server_handle_call_args), + "::", + stringify!(request_type) + ) + ); +} +pub type sgxsd_server_handle_call_args_t = sgxsd_server_handle_call_args; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_server_terminate_args {} +#[test] +fn bindgen_test_layout_sgxsd_server_terminate_args() { + assert_eq!( + ::core::mem::size_of::(), + 0usize, + concat!("Size of: ", stringify!(sgxsd_server_terminate_args)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_server_terminate_args)) + ); +} +pub type sgxsd_server_terminate_args_t = sgxsd_server_terminate_args; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_ra_get_quote_args { + pub args: *const libc::c_void, +} +#[test] +fn bindgen_test_layout_sgxsd_ra_get_quote_args() { + assert_eq!( + ::core::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(sgxsd_ra_get_quote_args)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_ra_get_quote_args)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).args as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_ra_get_quote_args), + "::", + stringify!(args) + ) + ); +} +impl Default for sgxsd_ra_get_quote_args { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgxsd_ra_get_quote_args_t = sgxsd_ra_get_quote_args; +pub type wchar_t = libc::c_int; +pub type mbstate_t = __mbstate_t; +pub type wint_t = __wint_t; +extern "C" { + pub fn btowc(arg1: libc::c_int) -> wint_t; +} +extern "C" { + pub fn wctob(arg1: wint_t) -> libc::c_int; +} +extern "C" { + pub fn mbrlen(arg1: *const libc::c_char, arg2: usize, arg3: *mut mbstate_t) -> usize; +} +extern "C" { + pub fn mbrtowc( + arg1: *mut wchar_t, + arg2: *const libc::c_char, + arg3: usize, + arg4: *mut mbstate_t, + ) -> usize; +} +extern "C" { + pub fn mbsinit(arg1: *const mbstate_t) -> libc::c_int; +} +extern "C" { + pub fn mbsrtowcs( + arg1: *mut wchar_t, + arg2: *mut *const libc::c_char, + arg3: usize, + arg4: *mut mbstate_t, + ) -> usize; +} +extern "C" { + pub fn wcrtomb(arg1: *mut libc::c_char, arg2: wchar_t, arg3: *mut mbstate_t) -> usize; +} +extern "C" { + pub fn wcschr(arg1: *const wchar_t, arg2: wchar_t) -> *mut wchar_t; +} +extern "C" { + pub fn wcscmp(arg1: *const wchar_t, arg2: *const wchar_t) -> libc::c_int; +} +extern "C" { + pub fn wcscoll(arg1: *const wchar_t, arg2: *const wchar_t) -> libc::c_int; +} +extern "C" { + pub fn wcscspn(arg1: *const wchar_t, arg2: *const wchar_t) -> usize; +} +extern "C" { + pub fn wcslen(arg1: *const wchar_t) -> usize; +} +extern "C" { + pub fn wcsncat(arg1: *mut wchar_t, arg2: *const wchar_t, arg3: usize) -> *mut wchar_t; +} +extern "C" { + pub fn wcsncmp(arg1: *const wchar_t, arg2: *const wchar_t, arg3: usize) -> libc::c_int; +} +extern "C" { + pub fn wcsncpy(arg1: *mut wchar_t, arg2: *const wchar_t, arg3: usize) -> *mut wchar_t; +} +extern "C" { + pub fn wcspbrk(arg1: *const wchar_t, arg2: *const wchar_t) -> *mut wchar_t; +} +extern "C" { + pub fn wcsrchr(arg1: *const wchar_t, arg2: wchar_t) -> *mut wchar_t; +} +extern "C" { + pub fn wcsrtombs( + arg1: *mut libc::c_char, + arg2: *mut *const wchar_t, + arg3: usize, + arg4: *mut mbstate_t, + ) -> usize; +} +extern "C" { + pub fn wcsspn(arg1: *const wchar_t, arg2: *const wchar_t) -> usize; +} +extern "C" { + pub fn wcsstr(arg1: *const wchar_t, arg2: *const wchar_t) -> *mut wchar_t; +} +extern "C" { + pub fn wcstok( + arg1: *mut wchar_t, + arg2: *const wchar_t, + arg3: *mut *mut wchar_t, + ) -> *mut wchar_t; +} +extern "C" { + pub fn wcsxfrm(arg1: *mut wchar_t, arg2: *const wchar_t, arg3: usize) -> usize; +} +extern "C" { + pub fn wmemchr(arg1: *const wchar_t, arg2: wchar_t, arg3: usize) -> *mut wchar_t; +} +extern "C" { + pub fn wmemcmp(arg1: *const wchar_t, arg2: *const wchar_t, arg3: usize) -> libc::c_int; +} +extern "C" { + pub fn wmemcpy(arg1: *mut wchar_t, arg2: *const wchar_t, arg3: usize) -> *mut wchar_t; +} +extern "C" { + pub fn wmemmove(arg1: *mut wchar_t, arg2: *const wchar_t, arg3: usize) -> *mut wchar_t; +} +extern "C" { + pub fn wmemset(arg1: *mut wchar_t, arg2: wchar_t, arg3: usize) -> *mut wchar_t; +} +extern "C" { + pub fn swprintf(arg1: *mut wchar_t, arg2: usize, arg3: *const wchar_t, ...) -> libc::c_int; +} +extern "C" { + pub fn vswprintf( + arg1: *mut wchar_t, + arg2: usize, + arg3: *const wchar_t, + arg4: *mut __va_list_tag, + ) -> libc::c_int; +} +extern "C" { + pub fn wcswcs(arg1: *const wchar_t, arg2: *const wchar_t) -> *mut wchar_t; +} +pub const SGX_SUCCESS: _status_t = 0; +pub const SGX_ERROR_UNEXPECTED: _status_t = 1; +pub const SGX_ERROR_INVALID_PARAMETER: _status_t = 2; +pub const SGX_ERROR_OUT_OF_MEMORY: _status_t = 3; +pub const SGX_ERROR_ENCLAVE_LOST: _status_t = 4; +pub const SGX_ERROR_INVALID_STATE: _status_t = 5; +pub const SGX_ERROR_FEATURE_NOT_SUPPORTED: _status_t = 8; +pub const SGX_ERROR_INVALID_FUNCTION: _status_t = 4097; +pub const SGX_ERROR_OUT_OF_TCS: _status_t = 4099; +pub const SGX_ERROR_ENCLAVE_CRASHED: _status_t = 4102; +pub const SGX_ERROR_ECALL_NOT_ALLOWED: _status_t = 4103; +pub const SGX_ERROR_OCALL_NOT_ALLOWED: _status_t = 4104; +pub const SGX_ERROR_STACK_OVERRUN: _status_t = 4105; +pub const SGX_ERROR_UNDEFINED_SYMBOL: _status_t = 8192; +pub const SGX_ERROR_INVALID_ENCLAVE: _status_t = 8193; +pub const SGX_ERROR_INVALID_ENCLAVE_ID: _status_t = 8194; +pub const SGX_ERROR_INVALID_SIGNATURE: _status_t = 8195; +pub const SGX_ERROR_NDEBUG_ENCLAVE: _status_t = 8196; +pub const SGX_ERROR_OUT_OF_EPC: _status_t = 8197; +pub const SGX_ERROR_NO_DEVICE: _status_t = 8198; +pub const SGX_ERROR_MEMORY_MAP_CONFLICT: _status_t = 8199; +pub const SGX_ERROR_INVALID_METADATA: _status_t = 8201; +pub const SGX_ERROR_DEVICE_BUSY: _status_t = 8204; +pub const SGX_ERROR_INVALID_VERSION: _status_t = 8205; +pub const SGX_ERROR_MODE_INCOMPATIBLE: _status_t = 8206; +pub const SGX_ERROR_ENCLAVE_FILE_ACCESS: _status_t = 8207; +pub const SGX_ERROR_INVALID_MISC: _status_t = 8208; +pub const SGX_ERROR_INVALID_LAUNCH_TOKEN: _status_t = 8209; +pub const SGX_ERROR_MAC_MISMATCH: _status_t = 12289; +pub const SGX_ERROR_INVALID_ATTRIBUTE: _status_t = 12290; +pub const SGX_ERROR_INVALID_CPUSVN: _status_t = 12291; +pub const SGX_ERROR_INVALID_ISVSVN: _status_t = 12292; +pub const SGX_ERROR_INVALID_KEYNAME: _status_t = 12293; +pub const SGX_ERROR_SERVICE_UNAVAILABLE: _status_t = 16385; +pub const SGX_ERROR_SERVICE_TIMEOUT: _status_t = 16386; +pub const SGX_ERROR_AE_INVALID_EPIDBLOB: _status_t = 16387; +pub const SGX_ERROR_SERVICE_INVALID_PRIVILEGE: _status_t = 16388; +pub const SGX_ERROR_EPID_MEMBER_REVOKED: _status_t = 16389; +pub const SGX_ERROR_UPDATE_NEEDED: _status_t = 16390; +pub const SGX_ERROR_NETWORK_FAILURE: _status_t = 16391; +pub const SGX_ERROR_AE_SESSION_INVALID: _status_t = 16392; +pub const SGX_ERROR_BUSY: _status_t = 16394; +pub const SGX_ERROR_MC_NOT_FOUND: _status_t = 16396; +pub const SGX_ERROR_MC_NO_ACCESS_RIGHT: _status_t = 16397; +pub const SGX_ERROR_MC_USED_UP: _status_t = 16398; +pub const SGX_ERROR_MC_OVER_QUOTA: _status_t = 16399; +pub const SGX_ERROR_KDF_MISMATCH: _status_t = 16401; +pub const SGX_ERROR_UNRECOGNIZED_PLATFORM: _status_t = 16402; +pub const SGX_ERROR_UNSUPPORTED_CONFIG: _status_t = 16403; +pub const SGX_ERROR_NO_PRIVILEGE: _status_t = 20482; +pub const SGX_ERROR_PCL_ENCRYPTED: _status_t = 24577; +pub const SGX_ERROR_PCL_NOT_ENCRYPTED: _status_t = 24578; +pub const SGX_ERROR_PCL_MAC_MISMATCH: _status_t = 24579; +pub const SGX_ERROR_PCL_SHA_MISMATCH: _status_t = 24580; +pub const SGX_ERROR_PCL_GUID_MISMATCH: _status_t = 24581; +pub const SGX_ERROR_FILE_BAD_STATUS: _status_t = 28673; +pub const SGX_ERROR_FILE_NO_KEY_ID: _status_t = 28674; +pub const SGX_ERROR_FILE_NAME_MISMATCH: _status_t = 28675; +pub const SGX_ERROR_FILE_NOT_SGX_FILE: _status_t = 28676; +pub const SGX_ERROR_FILE_CANT_OPEN_RECOVERY_FILE: _status_t = 28677; +pub const SGX_ERROR_FILE_CANT_WRITE_RECOVERY_FILE: _status_t = 28678; +pub const SGX_ERROR_FILE_RECOVERY_NEEDED: _status_t = 28679; +pub const SGX_ERROR_FILE_FLUSH_FAILED: _status_t = 28680; +pub const SGX_ERROR_FILE_CLOSE_FAILED: _status_t = 28681; +pub const SGX_ERROR_UNSUPPORTED_ATT_KEY_ID: _status_t = 32769; +pub const SGX_ERROR_ATT_KEY_CERTIFICATION_FAILURE: _status_t = 32770; +pub const SGX_ERROR_ATT_KEY_UNINITIALIZED: _status_t = 32771; +pub const SGX_ERROR_INVALID_ATT_KEY_CERT_DATA: _status_t = 32772; +pub const SGX_ERROR_PLATFORM_CERT_UNAVAILABLE: _status_t = 32773; +pub const SGX_INTERNAL_ERROR_ENCLAVE_CREATE_INTERRUPTED: _status_t = 61441; +pub type _status_t = u32; +pub use self::_status_t as sgx_status_t; +pub type sgx_enclave_id_t = u64; +extern "C" { + pub fn sgx_ocalloc(size: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn sgx_ocalloc_switchless(size: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn sgx_ocfree(); +} +extern "C" { + pub fn sgx_ocfree_switchless(); +} +extern "C" { + pub fn sgx_ecall( + eid: sgx_enclave_id_t, + index: libc::c_int, + ocall_table: *const libc::c_void, + ms: *mut libc::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_ecall_switchless( + eid: sgx_enclave_id_t, + index: libc::c_int, + ocall_table: *const libc::c_void, + ms: *mut libc::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_ocall(index: libc::c_uint, ms: *mut libc::c_void) -> sgx_status_t; +} +extern "C" { + pub fn sgx_ocall_switchless(index: libc::c_uint, ms: *mut libc::c_void) -> sgx_status_t; +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _attributes_t { + pub flags: u64, + pub xfrm: u64, +} +#[test] +fn bindgen_test_layout__attributes_t() { + assert_eq!( + ::core::mem::size_of::<_attributes_t>(), + 16usize, + concat!("Size of: ", stringify!(_attributes_t)) + ); + assert_eq!( + ::core::mem::align_of::<_attributes_t>(), + 8usize, + concat!("Alignment of ", stringify!(_attributes_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_attributes_t>())).flags as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_attributes_t), + "::", + stringify!(flags) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_attributes_t>())).xfrm as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_attributes_t), + "::", + stringify!(xfrm) + ) + ); +} +pub type sgx_attributes_t = _attributes_t; +pub type sgx_misc_select_t = u32; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_misc_attribute_t { + pub secs_attr: sgx_attributes_t, + pub misc_select: sgx_misc_select_t, +} +#[test] +fn bindgen_test_layout__sgx_misc_attribute_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_misc_attribute_t>(), + 24usize, + concat!("Size of: ", stringify!(_sgx_misc_attribute_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_misc_attribute_t>(), + 8usize, + concat!("Alignment of ", stringify!(_sgx_misc_attribute_t)) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<_sgx_misc_attribute_t>())).secs_attr as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_misc_attribute_t), + "::", + stringify!(secs_attr) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<_sgx_misc_attribute_t>())).misc_select as *const _ as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_sgx_misc_attribute_t), + "::", + stringify!(misc_select) + ) + ); +} +pub type sgx_misc_attribute_t = _sgx_misc_attribute_t; +pub type sgx_key_128bit_t = [u8; 16usize]; +pub type sgx_isv_svn_t = u16; +pub type sgx_config_svn_t = u16; +pub type sgx_config_id_t = [u8; 64usize]; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_cpu_svn_t { + pub svn: [u8; 16usize], +} +#[test] +fn bindgen_test_layout__sgx_cpu_svn_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_cpu_svn_t>(), + 16usize, + concat!("Size of: ", stringify!(_sgx_cpu_svn_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_cpu_svn_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_cpu_svn_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_cpu_svn_t>())).svn as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_cpu_svn_t), + "::", + stringify!(svn) + ) + ); +} +pub type sgx_cpu_svn_t = _sgx_cpu_svn_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_key_id_t { + pub id: [u8; 32usize], +} +#[test] +fn bindgen_test_layout__sgx_key_id_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_key_id_t>(), + 32usize, + concat!("Size of: ", stringify!(_sgx_key_id_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_key_id_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_key_id_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_key_id_t>())).id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_key_id_t), + "::", + stringify!(id) + ) + ); +} +pub type sgx_key_id_t = _sgx_key_id_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _key_request_t { + pub key_name: u16, + pub key_policy: u16, + pub isv_svn: sgx_isv_svn_t, + pub reserved1: u16, + pub cpu_svn: sgx_cpu_svn_t, + pub attribute_mask: sgx_attributes_t, + pub key_id: sgx_key_id_t, + pub misc_mask: sgx_misc_select_t, + pub config_svn: sgx_config_svn_t, + pub reserved2: [u8; 434usize], +} +#[test] +fn bindgen_test_layout__key_request_t() { + assert_eq!( + ::core::mem::size_of::<_key_request_t>(), + 512usize, + concat!("Size of: ", stringify!(_key_request_t)) + ); + assert_eq!( + ::core::mem::align_of::<_key_request_t>(), + 8usize, + concat!("Alignment of ", stringify!(_key_request_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).key_name as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(key_name) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).key_policy as *const _ as usize }, + 2usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(key_policy) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).isv_svn as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(isv_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).reserved1 as *const _ as usize }, + 6usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(reserved1) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).cpu_svn as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(cpu_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).attribute_mask as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(attribute_mask) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).key_id as *const _ as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(key_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).misc_mask as *const _ as usize }, + 72usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(misc_mask) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).config_svn as *const _ as usize }, + 76usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(config_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).reserved2 as *const _ as usize }, + 78usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(reserved2) + ) + ); +} +impl Default for _key_request_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_key_request_t = _key_request_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_measurement_t { + pub m: [u8; 32usize], +} +#[test] +fn bindgen_test_layout__sgx_measurement_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_measurement_t>(), + 32usize, + concat!("Size of: ", stringify!(_sgx_measurement_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_measurement_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_measurement_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_measurement_t>())).m as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_measurement_t), + "::", + stringify!(m) + ) + ); +} +pub type sgx_measurement_t = _sgx_measurement_t; +pub type sgx_mac_t = [u8; 16usize]; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _sgx_report_data_t { + pub d: [u8; 64usize], +} +#[test] +fn bindgen_test_layout__sgx_report_data_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_report_data_t>(), + 64usize, + concat!("Size of: ", stringify!(_sgx_report_data_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_report_data_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_report_data_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_report_data_t>())).d as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_report_data_t), + "::", + stringify!(d) + ) + ); +} +impl Default for _sgx_report_data_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_report_data_t = _sgx_report_data_t; +pub type sgx_prod_id_t = u16; +pub type sgx_isvext_prod_id_t = [u8; 16usize]; +pub type sgx_isvfamily_id_t = [u8; 16usize]; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _target_info_t { + pub mr_enclave: sgx_measurement_t, + pub attributes: sgx_attributes_t, + pub reserved1: [u8; 2usize], + pub config_svn: sgx_config_svn_t, + pub misc_select: sgx_misc_select_t, + pub reserved2: [u8; 8usize], + pub config_id: sgx_config_id_t, + pub reserved3: [u8; 384usize], +} +#[test] +fn bindgen_test_layout__target_info_t() { + assert_eq!( + ::core::mem::size_of::<_target_info_t>(), + 512usize, + concat!("Size of: ", stringify!(_target_info_t)) + ); + assert_eq!( + ::core::mem::align_of::<_target_info_t>(), + 8usize, + concat!("Alignment of ", stringify!(_target_info_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).mr_enclave as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(mr_enclave) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).attributes as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(attributes) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).reserved1 as *const _ as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(reserved1) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).config_svn as *const _ as usize }, + 50usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(config_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).misc_select as *const _ as usize }, + 52usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(misc_select) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).reserved2 as *const _ as usize }, + 56usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(reserved2) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).config_id as *const _ as usize }, + 64usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(config_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).reserved3 as *const _ as usize }, + 128usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(reserved3) + ) + ); +} +impl Default for _target_info_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_target_info_t = _target_info_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _report_body_t { + pub cpu_svn: sgx_cpu_svn_t, + pub misc_select: sgx_misc_select_t, + pub reserved1: [u8; 12usize], + pub isv_ext_prod_id: sgx_isvext_prod_id_t, + pub attributes: sgx_attributes_t, + pub mr_enclave: sgx_measurement_t, + pub reserved2: [u8; 32usize], + pub mr_signer: sgx_measurement_t, + pub reserved3: [u8; 32usize], + pub config_id: sgx_config_id_t, + pub isv_prod_id: sgx_prod_id_t, + pub isv_svn: sgx_isv_svn_t, + pub config_svn: sgx_config_svn_t, + pub reserved4: [u8; 42usize], + pub isv_family_id: sgx_isvfamily_id_t, + pub report_data: sgx_report_data_t, +} +#[test] +fn bindgen_test_layout__report_body_t() { + assert_eq!( + ::core::mem::size_of::<_report_body_t>(), + 384usize, + concat!("Size of: ", stringify!(_report_body_t)) + ); + assert_eq!( + ::core::mem::align_of::<_report_body_t>(), + 8usize, + concat!("Alignment of ", stringify!(_report_body_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).cpu_svn as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(cpu_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).misc_select as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(misc_select) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).reserved1 as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved1) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).isv_ext_prod_id as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_ext_prod_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).attributes as *const _ as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(attributes) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).mr_enclave as *const _ as usize }, + 64usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(mr_enclave) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).reserved2 as *const _ as usize }, + 96usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved2) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).mr_signer as *const _ as usize }, + 128usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(mr_signer) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).reserved3 as *const _ as usize }, + 160usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved3) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).config_id as *const _ as usize }, + 192usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(config_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).isv_prod_id as *const _ as usize }, + 256usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_prod_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).isv_svn as *const _ as usize }, + 258usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).config_svn as *const _ as usize }, + 260usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(config_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).reserved4 as *const _ as usize }, + 262usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved4) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).isv_family_id as *const _ as usize }, + 304usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_family_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).report_data as *const _ as usize }, + 320usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(report_data) + ) + ); +} +impl Default for _report_body_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_report_body_t = _report_body_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _report_t { + pub body: sgx_report_body_t, + pub key_id: sgx_key_id_t, + pub mac: sgx_mac_t, +} +#[test] +fn bindgen_test_layout__report_t() { + assert_eq!( + ::core::mem::size_of::<_report_t>(), + 432usize, + concat!("Size of: ", stringify!(_report_t)) + ); + assert_eq!( + ::core::mem::align_of::<_report_t>(), + 8usize, + concat!("Alignment of ", stringify!(_report_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_t>())).body as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_report_t), + "::", + stringify!(body) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_t>())).key_id as *const _ as usize }, + 384usize, + concat!( + "Offset of field: ", + stringify!(_report_t), + "::", + stringify!(key_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_t>())).mac as *const _ as usize }, + 416usize, + concat!( + "Offset of field: ", + stringify!(_report_t), + "::", + stringify!(mac) + ) + ); +} +impl Default for _report_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_report_t = _report_t; +pub type sgx_epid_group_id_t = [u8; 4usize]; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _spid_t { + pub id: [u8; 16usize], +} +#[test] +fn bindgen_test_layout__spid_t() { + assert_eq!( + ::core::mem::size_of::<_spid_t>(), + 16usize, + concat!("Size of: ", stringify!(_spid_t)) + ); + assert_eq!( + ::core::mem::align_of::<_spid_t>(), + 1usize, + concat!("Alignment of ", stringify!(_spid_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_spid_t>())).id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_spid_t), + "::", + stringify!(id) + ) + ); +} +pub type sgx_spid_t = _spid_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _basename_t { + pub name: [u8; 32usize], +} +#[test] +fn bindgen_test_layout__basename_t() { + assert_eq!( + ::core::mem::size_of::<_basename_t>(), + 32usize, + concat!("Size of: ", stringify!(_basename_t)) + ); + assert_eq!( + ::core::mem::align_of::<_basename_t>(), + 1usize, + concat!("Alignment of ", stringify!(_basename_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_basename_t>())).name as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_basename_t), + "::", + stringify!(name) + ) + ); +} +pub type sgx_basename_t = _basename_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _quote_nonce { + pub rand: [u8; 16usize], +} +#[test] +fn bindgen_test_layout__quote_nonce() { + assert_eq!( + ::core::mem::size_of::<_quote_nonce>(), + 16usize, + concat!("Size of: ", stringify!(_quote_nonce)) + ); + assert_eq!( + ::core::mem::align_of::<_quote_nonce>(), + 1usize, + concat!("Alignment of ", stringify!(_quote_nonce)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_quote_nonce>())).rand as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_quote_nonce), + "::", + stringify!(rand) + ) + ); +} +pub type sgx_quote_nonce_t = _quote_nonce; +pub const SGX_UNLINKABLE_SIGNATURE: sgx_quote_sign_type_t = 0; +pub const SGX_LINKABLE_SIGNATURE: sgx_quote_sign_type_t = 1; +pub type sgx_quote_sign_type_t = u32; +#[repr(C, packed)] +pub struct _quote_t { + pub version: u16, + pub sign_type: u16, + pub epid_group_id: sgx_epid_group_id_t, + pub qe_svn: sgx_isv_svn_t, + pub pce_svn: sgx_isv_svn_t, + pub xeid: u32, + pub basename: sgx_basename_t, + pub report_body: sgx_report_body_t, + pub signature_len: u32, + pub signature: __IncompleteArrayField, +} +#[test] +fn bindgen_test_layout__quote_t() { + assert_eq!( + ::core::mem::size_of::<_quote_t>(), + 436usize, + concat!("Size of: ", stringify!(_quote_t)) + ); + assert_eq!( + ::core::mem::align_of::<_quote_t>(), + 1usize, + concat!("Alignment of ", stringify!(_quote_t)) + ); +} +impl Default for _quote_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_quote_t = _quote_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _platform_info { + pub platform_info: [u8; 101usize], +} +#[test] +fn bindgen_test_layout__platform_info() { + assert_eq!( + ::core::mem::size_of::<_platform_info>(), + 101usize, + concat!("Size of: ", stringify!(_platform_info)) + ); + assert_eq!( + ::core::mem::align_of::<_platform_info>(), + 1usize, + concat!("Alignment of ", stringify!(_platform_info)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_platform_info>())).platform_info as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_platform_info), + "::", + stringify!(platform_info) + ) + ); +} +impl Default for _platform_info { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_platform_info_t = _platform_info; +#[repr(C, packed)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _update_info_bit { + pub ucodeUpdate: libc::c_int, + pub csmeFwUpdate: libc::c_int, + pub pswUpdate: libc::c_int, +} +#[test] +fn bindgen_test_layout__update_info_bit() { + assert_eq!( + ::core::mem::size_of::<_update_info_bit>(), + 12usize, + concat!("Size of: ", stringify!(_update_info_bit)) + ); + assert_eq!( + ::core::mem::align_of::<_update_info_bit>(), + 1usize, + concat!("Alignment of ", stringify!(_update_info_bit)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_update_info_bit>())).ucodeUpdate as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_update_info_bit), + "::", + stringify!(ucodeUpdate) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_update_info_bit>())).csmeFwUpdate as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(_update_info_bit), + "::", + stringify!(csmeFwUpdate) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_update_info_bit>())).pswUpdate as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_update_info_bit), + "::", + stringify!(pswUpdate) + ) + ); +} +pub type sgx_update_info_bit_t = _update_info_bit; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _att_key_id_t { + pub att_key_id: [u8; 256usize], +} +#[test] +fn bindgen_test_layout__att_key_id_t() { + assert_eq!( + ::core::mem::size_of::<_att_key_id_t>(), + 256usize, + concat!("Size of: ", stringify!(_att_key_id_t)) + ); + assert_eq!( + ::core::mem::align_of::<_att_key_id_t>(), + 1usize, + concat!("Alignment of ", stringify!(_att_key_id_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_att_key_id_t>())).att_key_id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_att_key_id_t), + "::", + stringify!(att_key_id) + ) + ); +} +impl Default for _att_key_id_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_att_key_id_t = _att_key_id_t; +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct _qe_report_info_t { + pub nonce: sgx_quote_nonce_t, + pub app_enclave_target_info: sgx_target_info_t, + pub qe_report: sgx_report_t, +} +#[test] +fn bindgen_test_layout__qe_report_info_t() { + assert_eq!( + ::core::mem::size_of::<_qe_report_info_t>(), + 960usize, + concat!("Size of: ", stringify!(_qe_report_info_t)) + ); + assert_eq!( + ::core::mem::align_of::<_qe_report_info_t>(), + 1usize, + concat!("Alignment of ", stringify!(_qe_report_info_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_qe_report_info_t>())).nonce as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_qe_report_info_t), + "::", + stringify!(nonce) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<_qe_report_info_t>())).app_enclave_target_info as *const _ + as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_qe_report_info_t), + "::", + stringify!(app_enclave_target_info) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_qe_report_info_t>())).qe_report as *const _ as usize }, + 528usize, + concat!( + "Offset of field: ", + stringify!(_qe_report_info_t), + "::", + stringify!(qe_report) + ) + ); +} +impl Default for _qe_report_info_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_qe_report_info_t = _qe_report_info_t; +pub type va_list = __va_list; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct div_t { + pub quot: libc::c_int, + pub rem: libc::c_int, +} +#[test] +fn bindgen_test_layout_div_t() { + assert_eq!( + ::core::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(div_t)) + ); + assert_eq!( + ::core::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(div_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(div_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).rem as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(div_t), + "::", + stringify!(rem) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct ldiv_t { + pub quot: libc::c_long, + pub rem: libc::c_long, +} +#[test] +fn bindgen_test_layout_ldiv_t() { + assert_eq!( + ::core::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(ldiv_t)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(ldiv_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(ldiv_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).rem as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(ldiv_t), + "::", + stringify!(rem) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct lldiv_t { + pub quot: libc::c_longlong, + pub rem: libc::c_longlong, +} +#[test] +fn bindgen_test_layout_lldiv_t() { + assert_eq!( + ::core::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(lldiv_t)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(lldiv_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(lldiv_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).rem as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(lldiv_t), + "::", + stringify!(rem) + ) + ); +} +extern "C" { + pub fn abort(); +} +extern "C" { + pub fn atexit(arg1: ::core::option::Option) -> libc::c_int; +} +extern "C" { + pub fn abs(arg1: libc::c_int) -> libc::c_int; +} +extern "C" { + pub fn atof(arg1: *const libc::c_char) -> f64; +} +extern "C" { + pub fn atoi(arg1: *const libc::c_char) -> libc::c_int; +} +extern "C" { + pub fn atol(arg1: *const libc::c_char) -> libc::c_long; +} +extern "C" { + pub fn bsearch( + arg1: *const libc::c_void, + arg2: *const libc::c_void, + arg3: usize, + arg4: usize, + arg5: ::core::option::Option< + unsafe extern "C" fn( + arg1: *const libc::c_void, + arg2: *const libc::c_void, + ) -> libc::c_int, + >, + ) -> *mut libc::c_void; +} +extern "C" { + pub fn calloc(arg1: usize, arg2: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn div(arg1: libc::c_int, arg2: libc::c_int) -> div_t; +} +extern "C" { + pub fn free(arg1: *mut libc::c_void); +} +extern "C" { + pub fn labs(arg1: libc::c_long) -> libc::c_long; +} +extern "C" { + pub fn ldiv(arg1: libc::c_long, arg2: libc::c_long) -> ldiv_t; +} +extern "C" { + pub fn malloc(arg1: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn memalign(arg1: usize, arg2: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn qsort( + arg1: *mut libc::c_void, + arg2: usize, + arg3: usize, + arg4: ::core::option::Option< + unsafe extern "C" fn( + arg1: *const libc::c_void, + arg2: *const libc::c_void, + ) -> libc::c_int, + >, + ); +} +extern "C" { + pub fn realloc(arg1: *mut libc::c_void, arg2: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn strtod(arg1: *const libc::c_char, arg2: *mut *mut libc::c_char) -> f64; +} +extern "C" { + pub fn strtol( + arg1: *const libc::c_char, + arg2: *mut *mut libc::c_char, + arg3: libc::c_int, + ) -> libc::c_long; +} +extern "C" { + pub fn strtof(arg1: *const libc::c_char, arg2: *mut *mut libc::c_char) -> f32; +} +extern "C" { + pub fn atoll(arg1: *const libc::c_char) -> libc::c_longlong; +} +extern "C" { + pub fn llabs(arg1: libc::c_longlong) -> libc::c_longlong; +} +extern "C" { + pub fn lldiv(arg1: libc::c_longlong, arg2: libc::c_longlong) -> lldiv_t; +} +extern "C" { + pub fn strtoll( + arg1: *const libc::c_char, + arg2: *mut *mut libc::c_char, + arg3: libc::c_int, + ) -> libc::c_longlong; +} +extern "C" { + pub fn strtoul( + arg1: *const libc::c_char, + arg2: *mut *mut libc::c_char, + arg3: libc::c_int, + ) -> libc::c_ulong; +} +extern "C" { + pub fn strtold(arg1: *const libc::c_char, arg2: *mut *mut libc::c_char) -> u128; +} +extern "C" { + pub fn strtoull( + arg1: *const libc::c_char, + arg2: *mut *mut libc::c_char, + arg3: libc::c_int, + ) -> libc::c_ulonglong; +} +extern "C" { + pub fn mblen(arg1: *const libc::c_char, arg2: usize) -> libc::c_int; +} +extern "C" { + pub fn mbstowcs(arg1: *mut wchar_t, arg2: *const libc::c_char, arg3: usize) -> usize; +} +extern "C" { + pub fn wctomb(arg1: *mut libc::c_char, arg2: wchar_t) -> libc::c_int; +} +extern "C" { + pub fn mbtowc(arg1: *mut wchar_t, arg2: *const libc::c_char, arg3: usize) -> libc::c_int; +} +extern "C" { + pub fn wcstombs(arg1: *mut libc::c_char, arg2: *const wchar_t, arg3: usize) -> usize; +} +extern "C" { + pub fn alloca(arg1: usize) -> *mut libc::c_void; +} +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct _sgx_kss_config_t { + pub config_id: sgx_config_id_t, + pub config_svn: sgx_config_svn_t, +} +#[test] +fn bindgen_test_layout__sgx_kss_config_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_kss_config_t>(), + 66usize, + concat!("Size of: ", stringify!(_sgx_kss_config_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_kss_config_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_kss_config_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_kss_config_t>())).config_id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_kss_config_t), + "::", + stringify!(config_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_kss_config_t>())).config_svn as *const _ as usize }, + 64usize, + concat!( + "Offset of field: ", + stringify!(_sgx_kss_config_t), + "::", + stringify!(config_svn) + ) + ); +} +impl Default for _sgx_kss_config_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_kss_config_t = _sgx_kss_config_t; +pub type sgx_launch_token_t = [u8; 1024usize]; +extern "C" { + pub fn sgx_create_enclave( + file_name: *const libc::c_char, + debug: libc::c_int, + launch_token: *mut sgx_launch_token_t, + launch_token_updated: *mut libc::c_int, + enclave_id: *mut sgx_enclave_id_t, + misc_attr: *mut sgx_misc_attribute_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_create_enclave_ex( + file_name: *const libc::c_char, + debug: libc::c_int, + launch_token: *mut sgx_launch_token_t, + launch_token_updated: *mut libc::c_int, + enclave_id: *mut sgx_enclave_id_t, + misc_attr: *mut sgx_misc_attribute_t, + ex_features: u32, + ex_features_p: *mut *const libc::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_create_enclave_from_buffer_ex( + buffer: *mut u8, + buffer_size: usize, + debug: libc::c_int, + enclave_id: *mut sgx_enclave_id_t, + misc_attr: *mut sgx_misc_attribute_t, + ex_features: u32, + ex_features_p: *mut *const libc::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_create_encrypted_enclave( + file_name: *const libc::c_char, + debug: libc::c_int, + launch_token: *mut sgx_launch_token_t, + launch_token_updated: *mut libc::c_int, + enclave_id: *mut sgx_enclave_id_t, + misc_attr: *mut sgx_misc_attribute_t, + sealed_key: *mut u8, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_destroy_enclave(enclave_id: sgx_enclave_id_t) -> sgx_status_t; +} +extern "C" { + pub fn sgx_get_target_info( + enclave_id: sgx_enclave_id_t, + target_info: *mut sgx_target_info_t, + ) -> sgx_status_t; +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_aes_gcm_mac { + pub data: [u8; 16usize], +} +#[test] +fn bindgen_test_layout_sgxsd_aes_gcm_mac() { + assert_eq!( + ::core::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(sgxsd_aes_gcm_mac)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_aes_gcm_mac)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_aes_gcm_mac), + "::", + stringify!(data) + ) + ); +} +pub type sgxsd_aes_gcm_mac_t = sgxsd_aes_gcm_mac; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_aes_gcm_iv { + pub data: [u8; 12usize], +} +#[test] +fn bindgen_test_layout_sgxsd_aes_gcm_iv() { + assert_eq!( + ::core::mem::size_of::(), + 12usize, + concat!("Size of: ", stringify!(sgxsd_aes_gcm_iv)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_aes_gcm_iv)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_aes_gcm_iv), + "::", + stringify!(data) + ) + ); +} +pub type sgxsd_aes_gcm_iv_t = sgxsd_aes_gcm_iv; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_aes_gcm_key { + pub data: [u8; 32usize], +} +#[test] +fn bindgen_test_layout_sgxsd_aes_gcm_key() { + assert_eq!( + ::core::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(sgxsd_aes_gcm_key)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_aes_gcm_key)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_aes_gcm_key), + "::", + stringify!(data) + ) + ); +} +pub type sgxsd_aes_gcm_key_t = sgxsd_aes_gcm_key; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_curve25519_public_key { + pub x: [u8; 32usize], +} +#[test] +fn bindgen_test_layout_sgxsd_curve25519_public_key() { + assert_eq!( + ::core::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(sgxsd_curve25519_public_key)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_curve25519_public_key)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).x as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_curve25519_public_key), + "::", + stringify!(x) + ) + ); +} +pub type sgxsd_curve25519_public_key_t = sgxsd_curve25519_public_key; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_request_negotiation_request { + pub client_pubkey: sgxsd_curve25519_public_key_t, +} +#[test] +fn bindgen_test_layout_sgxsd_request_negotiation_request() { + assert_eq!( + ::core::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(sgxsd_request_negotiation_request)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!( + "Alignment of ", + stringify!(sgxsd_request_negotiation_request) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).client_pubkey as *const _ + as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_request), + "::", + stringify!(client_pubkey) + ) + ); +} +pub type sgxsd_request_negotiation_request_t = sgxsd_request_negotiation_request; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_pending_request_id { + pub data: [u8; 8usize], + pub iv: sgxsd_aes_gcm_iv_t, + pub mac: sgxsd_aes_gcm_mac_t, +} +#[test] +fn bindgen_test_layout_sgxsd_pending_request_id() { + assert_eq!( + ::core::mem::size_of::(), + 36usize, + concat!("Size of: ", stringify!(sgxsd_pending_request_id)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_pending_request_id)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_pending_request_id), + "::", + stringify!(data) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).iv as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_pending_request_id), + "::", + stringify!(iv) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).mac as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_pending_request_id), + "::", + stringify!(mac) + ) + ); +} +pub type sgxsd_pending_request_id_t = sgxsd_pending_request_id; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_request_negotiation_response { + pub server_static_pubkey: sgxsd_curve25519_public_key_t, + pub server_ephemeral_pubkey: sgxsd_curve25519_public_key_t, + pub encrypted_pending_request_id: sgxsd_request_negotiation_response__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_request_negotiation_response__bindgen_ty_1 { + pub data: [u8; 36usize], + pub iv: sgxsd_aes_gcm_iv_t, + pub mac: sgxsd_aes_gcm_mac_t, +} +#[test] +fn bindgen_test_layout_sgxsd_request_negotiation_response__bindgen_ty_1() { + assert_eq!( + ::core::mem::size_of::(), + 64usize, + concat!( + "Size of: ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1) + ) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!( + "Alignment of ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).data + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1), + "::", + stringify!(data) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).iv + as *const _ as usize + }, + 36usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1), + "::", + stringify!(iv) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).mac + as *const _ as usize + }, + 48usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1), + "::", + stringify!(mac) + ) + ); +} +impl Default for sgxsd_request_negotiation_response__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout_sgxsd_request_negotiation_response() { + assert_eq!( + ::core::mem::size_of::(), + 128usize, + concat!("Size of: ", stringify!(sgxsd_request_negotiation_response)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!( + "Alignment of ", + stringify!(sgxsd_request_negotiation_response) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).server_static_pubkey + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response), + "::", + stringify!(server_static_pubkey) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).server_ephemeral_pubkey + as *const _ as usize + }, + 32usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response), + "::", + stringify!(server_ephemeral_pubkey) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())) + .encrypted_pending_request_id as *const _ as usize + }, + 64usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response), + "::", + stringify!(encrypted_pending_request_id) + ) + ); +} +impl Default for sgxsd_request_negotiation_response { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgxsd_request_negotiation_response_t = sgxsd_request_negotiation_response; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_msg_tag { + pub __bindgen_anon_1: sgxsd_msg_tag__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union sgxsd_msg_tag__bindgen_ty_1 { + pub p_tag: *mut libc::c_void, + pub tag: u64, + _bindgen_union_align: u64, +} +#[test] +fn bindgen_test_layout_sgxsd_msg_tag__bindgen_ty_1() { + assert_eq!( + ::core::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(sgxsd_msg_tag__bindgen_ty_1)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_msg_tag__bindgen_ty_1)) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).p_tag as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_tag__bindgen_ty_1), + "::", + stringify!(p_tag) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).tag as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_tag__bindgen_ty_1), + "::", + stringify!(tag) + ) + ); +} +impl Default for sgxsd_msg_tag__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout_sgxsd_msg_tag() { + assert_eq!( + ::core::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(sgxsd_msg_tag)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_msg_tag)) + ); +} +impl Default for sgxsd_msg_tag { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgxsd_msg_tag_t = sgxsd_msg_tag; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_msg_header { + pub iv: sgxsd_aes_gcm_iv_t, + pub mac: sgxsd_aes_gcm_mac_t, + pub pending_request_id: sgxsd_pending_request_id_t, +} +#[test] +fn bindgen_test_layout_sgxsd_msg_header() { + assert_eq!( + ::core::mem::size_of::(), + 64usize, + concat!("Size of: ", stringify!(sgxsd_msg_header)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_msg_header)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).iv as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_header), + "::", + stringify!(iv) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).mac as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_header), + "::", + stringify!(mac) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).pending_request_id as *const _ as usize + }, + 28usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_header), + "::", + stringify!(pending_request_id) + ) + ); +} +pub type sgxsd_msg_header_t = sgxsd_msg_header; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_node_init_args { + pub pending_requests_table_order: u8, +} +#[test] +fn bindgen_test_layout_sgxsd_node_init_args() { + assert_eq!( + ::core::mem::size_of::(), + 1usize, + concat!("Size of: ", stringify!(sgxsd_node_init_args)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_node_init_args)) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).pending_requests_table_order + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_node_init_args), + "::", + stringify!(pending_requests_table_order) + ) + ); +} +pub type sgxsd_node_init_args_t = sgxsd_node_init_args; +pub type sgxsd_server_state_handle_t = u64; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_status { + pub ok: bool, + pub name: *const libc::c_char, + pub code: i64, +} +#[test] +fn bindgen_test_layout_sgxsd_status() { + assert_eq!( + ::core::mem::size_of::(), + 24usize, + concat!("Size of: ", stringify!(sgxsd_status)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_status)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).ok as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_status), + "::", + stringify!(ok) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).name as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_status), + "::", + stringify!(name) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).code as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_status), + "::", + stringify!(code) + ) + ); +} +impl Default for sgxsd_status { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgxsd_status_t = sgxsd_status; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_enclave { + pub id: sgx_enclave_id_t, + pub __bindgen_anon_1: sgxsd_enclave__bindgen_ty_1, + pub launch_token: sgx_launch_token_t, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union sgxsd_enclave__bindgen_ty_1 { + pub gid: sgx_epid_group_id_t, + pub gid32: u32, + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout_sgxsd_enclave__bindgen_ty_1() { + assert_eq!( + ::core::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(sgxsd_enclave__bindgen_ty_1)) + ); + assert_eq!( + ::core::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(sgxsd_enclave__bindgen_ty_1)) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).gid as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_enclave__bindgen_ty_1), + "::", + stringify!(gid) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).gid32 as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_enclave__bindgen_ty_1), + "::", + stringify!(gid32) + ) + ); +} +impl Default for sgxsd_enclave__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout_sgxsd_enclave() { + assert_eq!( + ::core::mem::size_of::(), + 1040usize, + concat!("Size of: ", stringify!(sgxsd_enclave)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_enclave)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_enclave), + "::", + stringify!(id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).launch_token as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_enclave), + "::", + stringify!(launch_token) + ) + ); +} +impl Default for sgxsd_enclave { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgxsd_enclave_t = sgxsd_enclave; +pub type sgxsd_start_callback_t = ::core::option::Option< + unsafe extern "C" fn(arg1: sgxsd_enclave_t, arg2: *mut __va_list_tag) -> sgxsd_status_t, +>; +extern "C" { + pub fn sgxsd_start( + enclave_path: *const libc::c_char, + debug: bool, + p_launch_token: *const sgx_launch_token_t, + p_node_init_args: *const sgxsd_node_init_args_t, + p_callback: sgxsd_start_callback_t, + ... + ) -> sgxsd_status_t; +} +extern "C" { + pub fn sgxsd_get_next_quote( + enclave_id: sgx_enclave_id_t, + spid: sgx_spid_t, + p_sig_rl: *const u8, + sig_rl_size: u32, + p_quote: *mut sgx_quote_t, + quote_size: u32, + ) -> sgxsd_status_t; +} +pub const SGXSD_ERROR_PENDING_REQUEST_NOT_FOUND: sgxsd_status_code = 65537; +pub type sgxsd_status_code = u32; +pub use self::sgxsd_status_code as sgxsd_status_code_t; +extern "C" { + pub fn kbupd_enclave_recv_untrusted_msg(data: *const u8, data_size: usize); +} +extern "C" { + pub fn sgxsd_enclave_node_init(p_args: *const sgxsd_node_init_args_t) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_get_next_report( + qe_target_info: sgx_target_info_t, + p_report: *mut sgx_report_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_set_current_quote() -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_negotiate_request( + p_request: *const sgxsd_request_negotiation_request_t, + p_response: *mut sgxsd_request_negotiation_response_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_server_start( + p_args: *const sgxsd_server_init_args_t, + state_handle: sgxsd_server_state_handle_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_server_call( + p_args: *const sgxsd_server_handle_call_args_t, + msg_header: *const sgxsd_msg_header_t, + msg_data: *mut u8, + msg_size: usize, + msg_tag: sgxsd_msg_tag_t, + state_handle: sgxsd_server_state_handle_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_server_stop( + p_args: *const sgxsd_server_terminate_args_t, + state_handle: sgxsd_server_state_handle_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn kbupd_enclave_ocall_recv_enclave_msg(data: *const u8, data_size: usize) -> sgx_status_t; +} +extern "C" { + pub fn kbupd_enclave_ocall_alloc( + retval: *mut *mut libc::c_void, + size: *mut usize, + ) -> sgx_status_t; +} +extern "C" { + pub fn kbupd_enclave_ocall_panic(msg: *const u8, msg_size: usize) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_ocall_reply( + retval: *mut sgx_status_t, + reply_header: *const sgxsd_msg_header_t, + reply_data: *const u8, + reply_data_size: usize, + msg_tag: sgxsd_msg_tag_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_oc_cpuidex( + cpuinfo: *mut libc::c_int, + leaf: libc::c_int, + subleaf: libc::c_int, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_thread_wait_untrusted_event_ocall( + retval: *mut libc::c_int, + self_: *const libc::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_thread_set_untrusted_event_ocall( + retval: *mut libc::c_int, + waiter: *const libc::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_thread_setwait_untrusted_events_ocall( + retval: *mut libc::c_int, + waiter: *const libc::c_void, + self_: *const libc::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_thread_set_multiple_untrusted_events_ocall( + retval: *mut libc::c_int, + waiters: *mut *const libc::c_void, + total: usize, + ) -> sgx_status_t; +} +pub type __builtin_va_list = [__va_list_tag; 1usize]; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct __va_list_tag { + pub gp_offset: libc::c_uint, + pub fp_offset: libc::c_uint, + pub overflow_arg_area: *mut libc::c_void, + pub reg_save_area: *mut libc::c_void, +} +#[test] +fn bindgen_test_layout___va_list_tag() { + assert_eq!( + ::core::mem::size_of::<__va_list_tag>(), + 24usize, + concat!("Size of: ", stringify!(__va_list_tag)) + ); + assert_eq!( + ::core::mem::align_of::<__va_list_tag>(), + 8usize, + concat!("Alignment of ", stringify!(__va_list_tag)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__va_list_tag>())).gp_offset as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(gp_offset) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__va_list_tag>())).fp_offset as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(fp_offset) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<__va_list_tag>())).overflow_arg_area as *const _ as usize + }, + 8usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(overflow_arg_area) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__va_list_tag>())).reg_save_area as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(reg_save_area) + ) + ); +} +impl Default for __va_list_tag { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} diff --git a/enclave/kbupd_enclave/src/ffi/ecalls.rs b/enclave/kbupd_enclave/src/ffi/ecalls.rs new file mode 100644 index 0000000..e164e3d --- /dev/null +++ b/enclave/kbupd_enclave/src/ffi/ecalls.rs @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::prelude::*; + +use std::cell::*; +use std::ptr::{NonNull}; +use std::slice; + +use prost::Message; +use sgx_ffi::untrusted_slice::{UntrustedSlice}; + +pub use super::bindgen_wrapper::{ + sgxsd_server_init_args_t as StartArgs, + sgxsd_server_handle_call_args_t as CallArgs, + sgxsd_server_terminate_args_t as StopArgs, + KBUPD_REQUEST_TYPE_ANY, + KBUPD_REQUEST_TYPE_BACKUP, + KBUPD_REQUEST_TYPE_RESTORE, + KBUPD_REQUEST_TYPE_DELETE, +}; +use super::bindgen_wrapper::{ + kbupd_enclave_ocall_recv_enclave_msg, + kbupd_enclave_ocall_alloc, +}; + +use crate::protobufs::kbupd::{UntrustedMessageBatch, UntrustedMessage, EnclaveMessageBatch, EnclaveMessage}; + +pub trait KbupdService { + fn untrusted_message(&mut self, message: UntrustedMessage); +} + +const ENCLAVE_MESSAGE_BUFFER_SIZE: usize = 10240; + +#[cfg(not(any(test, feature = "test")))] +pub fn with_buffer(fun: F) -> R +where F: FnOnce(&RefCell>>) -> R +{ + #[thread_local] + static ENCLAVE_MESSAGE_BUFFER: RefCell>> = RefCell::new(None); + + fun(&ENCLAVE_MESSAGE_BUFFER) +} + +#[cfg(any(test, feature = "test"))] +pub fn with_buffer(fun: F) -> R +where F: FnOnce(&RefCell>>) -> R +{ + thread_local! { + static ENCLAVE_MESSAGE_BUFFER: RefCell>> = RefCell::new(None); + } + ENCLAVE_MESSAGE_BUFFER.with(fun) +} + +pub fn kbupd_enclave_alloc_untrusted(mut size: usize) -> Result, ()> { + let mut p_data: *mut libc::c_void = std::ptr::null_mut(); + match unsafe { kbupd_enclave_ocall_alloc(&mut p_data, &mut size) } { + 0 => UntrustedSlice::new(p_data as *mut u8, size), + error => { + error!("ocall error allocating {} bytes from untrusted: {}", size, error); + Err(()) + } + } +} + +pub fn kbupd_enclave_recv_untrusted_msg(service: &mut S, p_data: *const u8, data_size: usize) +where S: KbupdService, +{ + let data = ECallSlice(NonNull::new(p_data as *mut _), data_size); + + match UntrustedMessageBatch::decode(data.as_ref()) { + Ok(batch) => { + for message in batch.messages { + service.untrusted_message(message); + } + } + Err(decode_error) => { + error!("error decoding untrusted messages: {}", decode_error); + } + } + kbupd_send_flush(); +} + +pub fn kbupd_send(message: EnclaveMessage) { + let batch = EnclaveMessageBatch { messages: vec![message] }; + let buffer_len = with_buffer(|buffer| buffer.borrow().as_ref().map(Vec::len).unwrap_or(0)); + if buffer_len.saturating_add(batch.encoded_len()) > ENCLAVE_MESSAGE_BUFFER_SIZE { + kbupd_send_flush(); + } + with_buffer(|buffer| { + let mut buffer_ref_mut = RefMut::map(buffer.borrow_mut(), |maybe_buffer: &mut Option>| { + maybe_buffer.get_or_insert_with(|| Vec::with_capacity(ENCLAVE_MESSAGE_BUFFER_SIZE)) + }); + let buffer_mut: &mut Vec = buffer_ref_mut.as_mut(); + assert!(batch.encode(buffer_mut).is_ok()); + }); +} + +pub fn kbupd_send_flush() { + let maybe_buffer = with_buffer(|buffer_tls| { + std::mem::replace(&mut *buffer_tls.borrow_mut(), Default::default()) + }); + let mut buffer = match maybe_buffer { + Some(buffer) => buffer, + None => return, + }; + if !buffer.is_empty() { + let ocall_res = unsafe { kbupd_enclave_ocall_recv_enclave_msg(buffer.as_ptr(), buffer.len()) }; + assert_eq!(ocall_res, 0); + } + buffer.truncate(0); + + with_buffer(|buffer_tls| { + if buffer_tls.borrow().as_ref().map(Vec::is_empty).unwrap_or(true) { + if buffer.capacity() == ENCLAVE_MESSAGE_BUFFER_SIZE { + *buffer_tls.borrow_mut() = Some(buffer); + } + } + }); +} + +struct ECallSlice(Option>, usize); +impl AsRef<[u8]> for ECallSlice { + fn as_ref(&self) -> &[u8] { + if self.1 != 0 { + if let Some(ptr) = self.0 { + unsafe { slice::from_raw_parts(ptr.as_ptr(), self.1) } + } else { + &[] + } + } else { + &[] + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::super::mocks; + use mockers::*; + + struct MockKbupdService {} + impl KbupdService for MockKbupdService { + fn untrusted_message(&mut self, _message: UntrustedMessage) { + } + } + + #[test] + fn kbupd_enclave_recv_untrusted_msg_empty() { + let mut kbupd_service = MockKbupdService {}; + kbupd_enclave_recv_untrusted_msg(&mut kbupd_service, std::ptr::null(), 0); + } + + #[test] + fn kbupd_enclave_recv_untrusted_msg_bad() { + let bad_requests: &[&[u8]] = &[ + // bad tag 0, types 0..=7, truncated + &[0x00], &[0x01], &[0x02], &[0x03], &[0x04], &[0x05], &[0x06], &[0x07], + // tag 1, bad types 0..=1, truncated + &[0x08], &[0x09], + // tag 1, type 2, truncated + &[0x0A], + // tag 1, bad types 3..=7, truncated + &[0x0B], &[0x0C], &[0x0D], &[0x0E], &[0x0F], + // tag 2, types 0..=7, truncated + &[0x10], &[0x11], &[0x12], &[0x13], &[0x14], &[0x15], &[0x16], &[0x17], + + // tag 1, bad type 0 + &[0x08, 0x00], + // tag 1, type 2, length 1, truncated + &[0x0A, 0x01], + // tag 1, type 2, length 2^64-1, truncated + &[0x0A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01], + // tag 1, type 2, length 1 (overlong varint), truncated + &[0x0A, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00], + &[0x0A, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02], + + // tag 2, type 0, bad varints + &[0x10, 0x80], + &[0x10, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80], + &[0x10, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00], + + // bad tag 0 (overlong varint), type 0 + &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00], + &[0x80, 0x80, 0x80, 0x00, 0x00], + + // bad tag 2^32, type 0 + &[0x80, 0x80, 0x80, 0x80, 0x10, 0x00], + // bad tag 2^64-1, type 0 + &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00], + // bad tag (bad varint) + &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80], + &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00], + ]; + let null_requests: &[&[u8]] = &[ + // empty + &[], + + // tag 1, type 2, length 0 (overlong varint) + &[0x0A, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00], + // tag 1, type 2, length 0 (overlong varint, extra bits ignored) + &[0x0A, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02], + // tag 1 (overlong varint), type 2, length 0 + &[0x8A, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00], + + // tag 2 (overlong varint), type 0 + &[0x90, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00], + // tag 2 (overlong varint, extra bits ignored), type 0 + &[0x90, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02, 0x00], + ]; + let requests = bad_requests.iter().chain(null_requests.iter()); + for request in requests.into_iter() { + let scenario = Scenario::new(); + mocks::expect_enclave_messages(&scenario, vec![]); + let mut kbupd_service = MockKbupdService {}; + kbupd_enclave_recv_untrusted_msg(&mut kbupd_service, request.as_ptr(), request.len()); + drop(scenario); + } + } +} diff --git a/enclave/kbupd_enclave/src/ffi/mocks.rs b/enclave/kbupd_enclave/src/ffi/mocks.rs new file mode 100644 index 0000000..8f2b633 --- /dev/null +++ b/enclave/kbupd_enclave/src/ffi/mocks.rs @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::cell::{RefCell}; +use std; + +use mockers::*; +use mockers::matchers::*; +use mockers_derive::mocked; +use prost::{Message}; + +use crate::protobufs; + +use super::bindgen_wrapper::{ + sgx_status_t, +}; + +// +// mock extern "C" functions +// + +thread_local! { + pub static KBUPD_ENCLAVE_OCALL_RECV_ENCLAVE_MSG: RefCell> = RefCell::new(None); + pub static KBUPD_ENCLAVE_OCALL_ALLOC: RefCell> = RefCell::new(None); +} + +#[mocked] +pub trait KbupdEnclaveOcallRecvEnclaveMsg { + fn enclave_message(&self, msg: protobufs::kbupd::enclave_message::Inner); + fn kbupd_enclave_ocall_recv_enclave_msg(&self) -> sgx_status_t; +} + +#[mocked] +pub trait KbupdEnclaveOcallAlloc { + fn kbupd_enclave_ocall_alloc(&self, size: usize) -> Result<(*mut ::std::os::raw::c_void, usize), sgx_status_t>; +} + +impl MatchArg for Box> { + fn matches(&self, arg: &protobufs::kbupd::enclave_message::Inner) -> Result<(), String> { (**self).matches(arg) } + fn describe(&self) -> String { (**self).describe() } +} +pub fn expect_enclave_messages(scenario: &Scenario, matchers: impl IntoIterator>>) { + let mock = test_ffi::mock_for(&KBUPD_ENCLAVE_OCALL_RECV_ENCLAVE_MSG, &scenario); + for matcher in matchers { + scenario.expect(mock.enclave_message(matcher).and_return(())); + } + scenario.expect(mock.kbupd_enclave_ocall_recv_enclave_msg().and_return_clone(0).times(..)); +} + +pub fn expect_kbupd_enclave_ocall_alloc(scenario: &Scenario, + request_size: usize, + returned_ptr: *mut libc::c_void, + returned_size: usize) { + assert_ne!(request_size, 0); + let mock = test_ffi::mock_for(&KBUPD_ENCLAVE_OCALL_ALLOC, &scenario); + scenario.expect(mock.kbupd_enclave_ocall_alloc( + eq(request_size) + ).and_return(Ok((returned_ptr, returned_size)))); + + sgx_ffi::mocks::expect_sgx_is_outside_enclave(scenario, returned_ptr as *const libc::c_void, returned_size, true); +} + +// +// mock extern "C" function implementations +// + +pub mod impls { + use super::*; + + struct OCallSlice(*const u8, usize); + impl AsRef<[u8]> for OCallSlice { + fn as_ref(&self) -> &[u8] { + if self.1 != 0 { + assert!(!self.0.is_null()); + unsafe { std::slice::from_raw_parts(self.0, self.1) } + } else { + unsafe { std::slice::from_raw_parts(1 as *const u8, 0) } + } + } + } + + #[no_mangle] + pub extern "C" fn kbupd_enclave_ocall_recv_enclave_msg(data: *const u8, data_size: usize) -> sgx_status_t { + let data = OCallSlice(data, data_size); + let batch = protobufs::kbupd::EnclaveMessageBatch::decode(data.as_ref()).expect("bad EnclaveMessageBatch from enclave"); + KBUPD_ENCLAVE_OCALL_RECV_ENCLAVE_MSG.with(|mock_cell| { + let mock_ref = mock_cell.borrow(); + let mock = mock_ref.as_ref().expect("no mock for kbupd_enclave_ocall_recv_enclave_msg"); + for msg in batch.messages { + match msg.inner.expect("empty EnclaveMessage from enclave") { + protobufs::kbupd::enclave_message::Inner::EnclaveLogSignal(log) => { + eprintln!("enclave log: {}", std::str::from_utf8(&log.message).unwrap()); + } + msg_inner => { + mock.enclave_message(msg_inner); + } + } + } + mock.kbupd_enclave_ocall_recv_enclave_msg() + }) + } + + #[no_mangle] + pub extern "C" fn kbupd_enclave_ocall_alloc(p_ptr_out: *mut *mut ::std::os::raw::c_void, p_size_in_out: *mut usize) -> sgx_status_t { + assert!(!p_ptr_out.is_null()); + assert!(!p_size_in_out.is_null()); + let size = unsafe { *p_size_in_out }; + assert_ne!(size, 0); + let res = KBUPD_ENCLAVE_OCALL_ALLOC.with(|mock| { + (mock.borrow().as_ref().expect("no mock for kbupd_enclave_ocall_alloc")) + .kbupd_enclave_ocall_alloc(size) + }); + match res { + Ok((ptr, size)) => { + unsafe { + *p_ptr_out = ptr; + *p_size_in_out = size; + } + 0 + } + Err(error) => error, + } + } + + #[no_mangle] + pub extern "C" fn curve25519_donna( + arg1: *mut ::std::os::raw::c_uchar, + arg2: *const ::std::os::raw::c_uchar, + arg3: *const ::std::os::raw::c_uchar, + ) -> ::std::os::raw::c_int { + let arg1 = unsafe { std::slice::from_raw_parts_mut(arg1, 32) }; + let arg2 = unsafe { std::slice::from_raw_parts(arg2, 32) }; + let arg3 = unsafe { std::slice::from_raw_parts(arg3, 32) }; + test_ffi::read_rand(arg1); + arg2.iter().for_each(|p| unsafe { std::ptr::read_volatile(p); }); + arg3.iter().for_each(|p| unsafe { std::ptr::read_volatile(p); }); + 0 + } +} diff --git a/enclave/kbupd_enclave/src/ffi/mod.rs b/enclave/kbupd_enclave/src/ffi/mod.rs new file mode 100644 index 0000000..1500849 --- /dev/null +++ b/enclave/kbupd_enclave/src/ffi/mod.rs @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#[allow(dead_code, non_camel_case_types, non_upper_case_globals, non_snake_case, improper_ctypes, clippy::all, clippy::pedantic, clippy::integer_arithmetic)] +mod bindgen_wrapper; +pub mod ecalls; +#[cfg(not(any(test, feature = "test")))] +mod panic; +pub mod snow_resolver; + +#[cfg(test)] pub mod mocks; diff --git a/enclave/kbupd_enclave/src/ffi/panic.rs b/enclave/kbupd_enclave/src/ffi/panic.rs new file mode 100644 index 0000000..0dfb248 --- /dev/null +++ b/enclave/kbupd_enclave/src/ffi/panic.rs @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use alloc::string::{ToString}; +use core::panic::{PanicInfo}; + +use super::bindgen_wrapper::{kbupd_enclave_ocall_panic}; + +#[panic_handler] +fn panic(info: &PanicInfo<'_>) -> ! { + let message = info.to_string(); + unsafe { + kbupd_enclave_ocall_panic(message.as_ptr(), message.len()); + libc::abort() + } +} diff --git a/enclave/kbupd_enclave/src/ffi/snow_resolver.rs b/enclave/kbupd_enclave/src/ffi/snow_resolver.rs new file mode 100644 index 0000000..065da45 --- /dev/null +++ b/enclave/kbupd_enclave/src/ffi/snow_resolver.rs @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use core::convert::{TryInto}; + +use crate::prelude::*; + +use rand_core::{RngCore, CryptoRng}; +use sgxsd_ffi::*; +use snow::resolvers::*; +use snow::types::*; +use snow::params::*; + +#[derive(Default)] +pub struct SnowResolver; + +#[derive(Default)] +struct SnowRdRand; + +#[derive(Default)] +struct SnowDh25519 { + key: Curve25519Key, +} + +#[derive(Default)] +struct SnowHashSHA256 { + context: SHA256Context, +} + +#[derive(Default)] +struct SnowCipherAESGCM { + key: AesGcmKey, +} + +// +// SnowResolver impls +// + +impl CryptoResolver for SnowResolver { + fn resolve_rng(&self) -> Option> { + Some(Box::new(SnowRdRand)) + } + + fn resolve_dh(&self, choice: &DHChoice) -> Option> { + match *choice { + DHChoice::Curve25519 => Some(Box::new(SnowDh25519::default())), + _ => None, + } + } + + fn resolve_hash(&self, choice: &HashChoice) -> Option> { + match *choice { + HashChoice::SHA256 => Some(Box::new(SnowHashSHA256::default())), + _ => None, + } + } + + fn resolve_cipher(&self, choice: &CipherChoice) -> Option> { + match *choice { + CipherChoice::AESGCM => Some(Box::new(SnowCipherAESGCM::default())), + _ => None, + } + } +} + +// +// SnowRdRand impls +// + +impl Random for SnowRdRand {} +impl CryptoRng for SnowRdRand {} +impl RngCore for SnowRdRand { + fn next_u32(&mut self) -> u32 { RdRand.next_u32() } + fn next_u64(&mut self) -> u64 { RdRand.next_u64() } + fn fill_bytes(&mut self, dest: &mut [u8]) { RdRand.fill_bytes(dest) } + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { RdRand.try_fill_bytes(dest) } +} + +// +// SnowDh25519 impls +// + +impl Dh for SnowDh25519 { + fn name(&self) -> &'static str { "25519" } + fn pub_len(&self) -> usize { 32 } + fn priv_len(&self) -> usize { 32 } + fn set(&mut self, privkey: &[u8]) { + let privkey: &[u8; 32] = privkey.try_into().unwrap_or_else(|_| panic!("overflow")); + self.key.set_key(privkey); + } + fn generate(&mut self, rng: &mut dyn Random) { + self.key.generate(rng); + } + fn pubkey(&self) -> &[u8] { + self.key.pubkey() + } + fn privkey(&self) -> &[u8] { + self.key.privkey() + } + fn dh(&self, pubkey: &[u8], out: &mut [u8]) -> Result<(), ()> { + let pubkey: &[u8] = pubkey.get(..32).unwrap_or_else(|| panic!("overflow")); + let pubkey: &[u8; 32] = pubkey.try_into().unwrap_or_else(|_| static_unreachable!()); + let out: &mut [u8] = out.get_mut(..32).unwrap_or_else(|| panic!("overflow")); + let out: &mut [u8; 32] = out.try_into().unwrap_or_else(|_| static_unreachable!()); + self.key.dh(pubkey, out); + Ok(()) + } +} + +// +// SnowCipherAESGCM impls +// + +impl Cipher for SnowCipherAESGCM { + fn name(&self) -> &'static str { "AESGCM" } + fn set(&mut self, key: &[u8]) { + let key: &[u8; 32] = key.try_into().unwrap_or_else(|_| panic!("overflow")); + self.key.set_key(key); + } + fn encrypt(&self, nonce: u64, authtext: &[u8], plaintext: &[u8], out: &mut [u8]) -> usize { + let (text_in_out, out) = out.split_at_mut(plaintext.len()); + let out: &mut [u8] = out.get_mut(..16).unwrap_or_else(|| panic!("overflow")); + let out: &mut [u8; 16] = out.try_into().unwrap_or_else(|_| static_unreachable!()); + text_in_out.copy_from_slice(plaintext); + + let mut mac = AesGcmMac::default(); + let mut iv = AesGcmIv::default(); + let iv_data: &mut [u8] = iv.data.get_mut(4..).unwrap_or_else(|| static_unreachable!()); + let iv_data: &mut [u8; 8] = iv_data.try_into().unwrap_or_else(|_| static_unreachable!()); + *iv_data = nonce.to_be_bytes(); + + match self.key.encrypt(text_in_out, authtext, &iv, &mut mac) { + Ok(()) => { + *out = mac.data; + text_in_out.len().saturating_add(mac.data.len()) + } + Err(_) => { + sgx_ffi::util::clear(text_in_out); + 0 + } + } + } + + fn decrypt(&self, nonce: u64, authtext: &[u8], ciphertext: &[u8], out: &mut [u8]) -> Result { + let ciphertext_len = ciphertext.len().checked_sub(16) + .unwrap_or_else(|| panic!("overflow")); + let (ciphertext, ciphertext_mac_data) = ciphertext.split_at(ciphertext_len); + let ciphertext_mac_data: &[u8; 16] = ciphertext_mac_data.try_into().unwrap_or_else(|_| unreachable!()); + let (in_out_text, _) = out.split_at_mut(ciphertext.len()); + in_out_text.copy_from_slice(ciphertext); + + let mac = AesGcmMac { data: *ciphertext_mac_data }; + let mut iv = AesGcmIv::default(); + let iv_data: &mut [u8] = iv.data.get_mut(4..).unwrap_or_else(|| static_unreachable!()); + let iv_data: &mut [u8; 8] = iv_data.try_into().unwrap_or_else(|_| static_unreachable!()); + *iv_data = nonce.to_be_bytes(); + + self.key.decrypt(in_out_text, authtext, &iv, &mac) + .map(|()| in_out_text.len()) + .map_err(drop) + } +} + +// +// SnowHashSHA256 impls +// + +impl Hash for SnowHashSHA256 { + fn name(&self) -> &'static str { "SHA256" } + fn block_len(&self) -> usize { 64 } + fn hash_len(&self) -> usize { SHA256Context::hash_len() } + fn reset(&mut self) { + self.context.reset(); + } + fn input(&mut self, data: &[u8]) { + self.context.update(data); + } + fn result(&mut self, out: &mut [u8]) { + let out: &mut [u8] = out.get_mut(..SHA256Context::hash_len()).unwrap_or_else(|| panic!("overflow")); + let out: &mut [u8; SHA256Context::hash_len()] = out.try_into().unwrap_or_else(|_| static_unreachable!()); + self.context.result(out); + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + + use mockers::*; + use mockers::matchers::*; + + #[test] + fn resolve_rng_test() { + let rand = SnowResolver.resolve_rng(); + assert!(rand.is_some()); + if let Some(mut rand) = rand { + let mut data = vec![0; 100]; + rand.fill_bytes(&mut data[..0]); + assert_eq!(&data[..], &vec![0; data.len()][..]); + rand.fill_bytes(&mut data); + assert_ne!(&data[..], &vec![0; data.len()][..]); + } + } + + #[test] + fn resolve_dh_test() { + let dh = SnowResolver.resolve_dh(&DHChoice::Curve25519); + let rand = SnowResolver.resolve_rng(); + assert!(dh.is_some()); + assert!(rand.is_some()); + if let (Some(mut dh), Some(mut rand)) = (dh, rand) { + dh.generate(&mut *rand); + let mut privkey = [0; 32]; + let mut pubkey = [0; 32]; + privkey.copy_from_slice(dh.privkey()); + pubkey.copy_from_slice(dh.pubkey()); + assert_ne!(&privkey, &[0; 32]); + assert_ne!(&pubkey, &[0; 32]); + + dh.set(&test_ffi::rand_bytes([0; 32])); + assert_ne!(dh.privkey(), privkey); + assert_ne!(dh.pubkey(), pubkey); + + let mut res = vec![0; 32]; + dh.dh(&test_ffi::rand_bytes(vec![0; 32]), &mut res).unwrap(); + assert_ne!(&res[..], &[0; 32][..]); + } + } + + #[test] + #[should_panic] + fn test_dh_set_overflow() { + let dh = SnowResolver.resolve_dh(&DHChoice::Curve25519); + if let Some(mut dh) = dh { + dh.set(&[0; 31]); + } + } + + #[test] + #[should_panic] + fn test_dh_pubkey_overflow() { + let dh = SnowResolver.resolve_dh(&DHChoice::Curve25519); + if let Some(dh) = dh { + let _ignore = dh.dh(&test_ffi::rand_bytes(vec![0; 31]), &mut vec![0; 32]); + } + } + + #[test] + #[should_panic] + fn test_dh_out_overflow() { + let dh = SnowResolver.resolve_dh(&DHChoice::Curve25519); + if let Some(dh) = dh { + let _ignore = dh.dh(&test_ffi::rand_bytes(vec![0; 32]), &mut vec![0; 31]); + } + } + + #[test] + fn resolve_hash_test() { + let hash = SnowResolver.resolve_hash(&HashChoice::SHA256); + assert!(hash.is_some()); + if let Some(mut hash) = hash { + hash.reset(); + hash.input(&[]); + hash.input(&[0]); + hash.input(&test_ffi::rand_bytes(vec![0; 100])); + let mut result = vec![0; hash.hash_len()]; + hash.result(&mut result); + assert_ne!(&result[..], &vec![0; hash.hash_len()][..]); + } + } + + macro_rules! eq_vec { + ($vec:expr) => ({ + let vec: Vec = $vec.clone(); + check(move |slice: &&[u8]| slice == &&vec[..]) + }) + } + + #[test] + fn resolve_cipher_test() { + let cipher = SnowResolver.resolve_cipher(&CipherChoice::AESGCM); + assert!(cipher.is_some()); + if let Some(mut cipher) = cipher { + let privkey = test_ffi::rand_bytes(vec![0; 32]); + cipher.set(&privkey); + + test_ffi::clear(&sgxsd_ffi::mocks::SGXSD_AES_GCM_ENCRYPT); + + let authtext = vec![0; 100]; + let plaintext = vec![0; 100]; + let mut out = vec![0; plaintext.len() + 16]; + assert_eq!(cipher.encrypt(0, &authtext, &plaintext, &mut out), out.len()); + assert_ne!(&vec![0; 8][..], &out[..8]); + assert_ne!(&vec![0; 8][..], &out[(out.len() - 8)..]); + + test_ffi::clear(&sgxsd_ffi::mocks::SGXSD_AES_GCM_DECRYPT); + + let ciphertext = vec![0; plaintext.len() + 16]; + let mut out = vec![0; plaintext.len()]; + assert_eq!(cipher.decrypt(0, &authtext, &ciphertext, &mut out), Ok(out.len())); + assert_ne!(&out[..], &vec![0; out.len()][..]); + } + } + + #[test] + fn resolve_cipher_success() { + let scenario = Scenario::new(); + let mut cipher = SnowResolver.resolve_cipher(&CipherChoice::AESGCM).unwrap(); + let privkey = test_ffi::rand_bytes(vec![0; 32]); + let authtext = test_ffi::rand_bytes(vec![0; 100]); + let plaintext = test_ffi::rand_bytes(vec![0; 100]); + let ciphertext = test_ffi::rand_bytes(vec![0; 100]); + + cipher.set(&privkey); + + let mock = test_ffi::mock_for(&sgxsd_ffi::mocks::SGXSD_AES_GCM_ENCRYPT, &scenario); + scenario.expect(mock.sgxsd_aes_gcm_encrypt( + eq_vec!(privkey), + eq_vec!(plaintext), + any(), + eq_vec!(authtext) + ).and_return(Ok(ciphertext.clone()))); + + let mut ciphertext_and_tag_out = vec![0; plaintext.len() + 16]; + assert_eq!(cipher.encrypt(0, &authtext, &plaintext, &mut ciphertext_and_tag_out), ciphertext_and_tag_out.len()); + assert_eq!(&ciphertext_and_tag_out[..ciphertext.len()], &ciphertext[..]); + + let mock = test_ffi::mock_for(&sgxsd_ffi::mocks::SGXSD_AES_GCM_DECRYPT, &scenario); + scenario.expect(mock.sgxsd_aes_gcm_decrypt( + eq_vec!(privkey), + eq_vec!(ciphertext), + any(), + eq_vec!(authtext) + ).and_return(Ok(plaintext.clone()))); + + let mut plaintext_out = vec![0; plaintext.len()]; + assert_eq!(cipher.decrypt(0, &authtext, &ciphertext_and_tag_out, &mut plaintext_out), Ok(plaintext_out.len())); + assert_eq!(&plaintext_out, &plaintext); + } + + #[test] + fn resolve_cipher_fail() { + let scenario = Scenario::new(); + let mut cipher = SnowResolver.resolve_cipher(&CipherChoice::AESGCM).unwrap(); + let privkey = test_ffi::rand_bytes(vec![0; 32]); + let authtext = test_ffi::rand_bytes(vec![0; 100]); + let plaintext = test_ffi::rand_bytes(vec![0; 100]); + let ciphertext = test_ffi::rand_bytes(vec![0; 100]); + + cipher.set(&privkey); + + let mock = test_ffi::mock_for(&sgxsd_ffi::mocks::SGXSD_AES_GCM_ENCRYPT, &scenario); + scenario.expect(mock.sgxsd_aes_gcm_encrypt( + eq_vec!(privkey), + eq_vec!(plaintext), + any(), + eq_vec!(authtext) + ).and_return(Err(()))); + + let mut ciphertext_and_tag_out = vec![0; plaintext.len() + 16]; + assert_eq!(cipher.encrypt(0, &authtext, &plaintext, &mut ciphertext_and_tag_out), 0); + assert_eq!(&ciphertext_and_tag_out, &vec![0; ciphertext_and_tag_out.len()]); + + let mock = test_ffi::mock_for(&sgxsd_ffi::mocks::SGXSD_AES_GCM_DECRYPT, &scenario); + scenario.expect(mock.sgxsd_aes_gcm_decrypt( + eq_vec!(privkey), + eq_vec!(ciphertext), + any(), + eq_vec!(authtext) + ).and_return(Err(()))); + + ciphertext_and_tag_out[..ciphertext.len()].copy_from_slice(&ciphertext); + let mut plaintext_out = ciphertext.clone(); + assert_eq!(cipher.decrypt(0, &authtext, &ciphertext_and_tag_out, &mut plaintext_out), Err(())); + assert_eq!(&plaintext_out, &ciphertext); + } +} diff --git a/enclave/kbupd_enclave/src/hasher.rs b/enclave/kbupd_enclave/src/hasher.rs new file mode 100644 index 0000000..5fddbfc --- /dev/null +++ b/enclave/kbupd_enclave/src/hasher.rs @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(deprecated)] + +use core::hash::{BuildHasher, SipHasher}; + +use rand_core::{RngCore}; +use sgxsd_ffi::{RdRand}; + +#[derive(Clone)] +pub struct DefaultHasher(u64, u64); + +impl Default for DefaultHasher { + fn default() -> Self { + Self(RdRand.next_u64(), RdRand.next_u64()) + } +} + +impl BuildHasher for DefaultHasher { + type Hasher = SipHasher; + fn build_hasher(&self) -> Self::Hasher { + SipHasher::new_with_keys(self.0, self.1) + } +} diff --git a/enclave/kbupd_enclave/src/kbupd_enclave.proto b/enclave/kbupd_enclave/src/kbupd_enclave.proto new file mode 100644 index 0000000..943de7e --- /dev/null +++ b/enclave/kbupd_enclave/src/kbupd_enclave.proto @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +syntax = "proto2"; + +package protobufs.kbupd_enclave; + +import "kbupd.proto"; +import "kbupd_client.proto"; +import "raft.proto"; + +message SecretBytes { + required bytes data = 1; +} + +// +// transactions +// + +message TransactionData { + oneof inner { + FrontendRequestTransaction frontend_request = 1; + StartXferTransaction start_xfer = 2; + SetSidTransaction set_sid = 3; + RemoveChunkTransaction remove_chunk = 4; + ApplyChunkTransaction apply_chunk = 5; + PauseXferTransaction pause_xfer = 6; + ResumeXferTransaction resume_xfer = 7; + FinishXferTransaction finish_xfer = 8; + SetTimeTransaction set_time = 9; + }; +} +message FrontendRequestTransaction { + required bytes from_node_id = 1; + required uint64 request_id = 2; + oneof transaction { + CreateBackupTransaction create = 3; + BackupTransaction backup = 4; + RestoreTransaction restore = 5; + DeleteBackupTransaction delete = 6; + }; +} + +message CreateBackupTransaction { + required kbupd.BackupId backup_id = 1; + required bytes new_creation_nonce = 2; + required bytes new_nonce = 3; +} +message BackupTransaction { + required kbupd.BackupId backup_id = 1; + required bytes old_nonce = 2; + required bytes new_creation_nonce = 3; + required bytes new_nonce = 4; + required SecretBytes data = 5; + required SecretBytes pin = 6; + required uint32 tries = 7; +} +message RestoreTransaction { + required kbupd.BackupId backup_id = 1; + required bytes creation_nonce = 2; + required bytes old_nonce = 3; + required bytes new_nonce = 4; + required SecretBytes pin = 5; +} +message DeleteBackupTransaction { + required kbupd.BackupId backup_id = 1; +} +message StartXferTransaction { + required bytes from_node_id = 1; + required XferRequest xfer_request = 2; +} +message SetSidTransaction { + required bytes from_node_id = 1; + required kbupd.ServiceId service_id = 2; +} +message RemoveChunkTransaction { + required bytes from_node_id = 1; + required XferChunkReply xfer_chunk_reply = 2; + required kbupd.BackupId chunk_last = 3; +} +message ApplyChunkTransaction { + required bytes from_node_id = 1; + required XferChunkRequest xfer_chunk_request = 2; + required XferChunkReply xfer_chunk_reply = 3; +} +message PauseXferTransaction { + required uint64 request_id = 1; +} +message ResumeXferTransaction { + required uint64 request_id = 1; + required kbupd.BackupId chunk_last = 2; +} +message FinishXferTransaction { + required uint64 request_id = 1; + required bool force = 2; +} +message SetTimeTransaction { + required uint64 now_secs = 1; +} + +// +// remote enclave handshake +// + +enum NodeType { + NODE_TYPE_NONE = 0; + NODE_TYPE_FRONTEND = 1; + NODE_TYPE_REPLICA = 2; +} + +message PeerConnectRequest { + required NodeType node_type = 1; + optional kbupd.IasReport ias_report = 2; + required bytes noise_data = 3; +} + +message PeerConnectReply { + required bytes sgx_quote = 1; + required bytes noise_data = 2; +} + +// +// enclave-to-enclave requests +// + +message EnclaveGetQuoteRequest { +} + +message EnclaveGetQuoteReply { + required bytes sgx_quote = 1; +} + +// +// frontend to replica +// + +message FrontendToReplicaMessage { + oneof inner { + TransactionRequest transaction_request = 1; + EnclaveGetQuoteRequest enclave_get_quote_request = 2; + }; +} + +message TransactionRequest { + required uint64 request_id = 1; + oneof data { + kbupd.CreateBackupRequest create = 2; + BackupTransactionRequest backup = 3; + RestoreTransactionRequest restore = 4; + DeleteTransactionRequest delete = 5; + }; +} + +message BackupTransactionRequest { + optional bytes service_id = 1; + required kbupd.BackupId backup_id = 2; + required bytes nonce = 3; + required uint64 valid_from = 4; + required SecretBytes data = 5; + required SecretBytes pin = 6; + required uint32 tries = 7; +} + +message RestoreTransactionRequest { + optional bytes service_id = 1; + required kbupd.BackupId backup_id = 2; + required bytes nonce = 3; + required uint64 valid_from = 4; + required SecretBytes pin = 5; +} + +message DeleteTransactionRequest { + optional bytes service_id = 1; + required kbupd.BackupId backup_id = 2; +} + +// +// replica to frontend +// + +message ReplicaToFrontendMessage { + oneof inner { + TransactionReply transaction_reply = 1; + EnclaveGetQuoteReply enclave_get_quote_reply = 2; + }; +} + +message TransactionReply { + required uint64 request_id = 1; + oneof data { + kbupd_client.Response client_response = 2; + kbupd.CreateBackupReply create_backup_reply = 3; + kbupd.DeleteBackupReply delete_backup_reply = 4; + TransactionErrorNotLeader not_leader = 5; + TransactionErrorWrongPartition wrong_partition = 6; + TransactionErrorServiceIdMismatch service_id_mismatch = 7; + TransactionErrorXferInProgress xfer_in_progress = 8; + TransactionErrorInvalidRequest invalid_request = 9; + TransactionErrorInternalError internal_error = 10; + }; +} + +message TransactionErrorNotLeader { + optional bytes leader_node_id = 1; + required raft.TermId term = 2; +} + +message TransactionErrorWrongPartition { + optional kbupd.PartitionKeyRangePB range = 1; + optional kbupd.PartitionConfig new_partition = 2; +} + +message TransactionErrorServiceIdMismatch { +} + +message TransactionErrorXferInProgress { +} + +message TransactionErrorInvalidRequest { +} + +message TransactionErrorInternalError { +} + +// +// replica to replica +// + +message ReplicaToReplicaMessage { + oneof inner { + raft.RaftMessage raft_message = 1; + CreateRaftGroupRequest create_raft_group_request = 2; + EnclaveGetQuoteRequest enclave_get_quote_request = 8; + EnclaveGetQuoteReply enclave_get_quote_reply = 9; + + XferRequest xfer_request = 3; + XferReply xfer_reply = 4; + XferChunkRequest xfer_chunk_request = 5; + XferChunkReply xfer_chunk_reply = 6; + XferErrorNotLeader xfer_error_not_leader = 7; + }; +} + +message CreateRaftGroupRequest { + optional kbupd.ServiceId service_id = 1; + required raft.RaftGroupId group_id = 2; + repeated bytes node_ids = 3; + required kbupd.EnclaveReplicaGroupConfig config = 4; + optional kbupd.SourcePartitionConfig source_partition = 5; +} + +message XferRequest { + required uint32 chunk_size = 1; + required kbupd.PartitionKeyRangePB full_range = 2; + repeated bytes node_ids = 3; + required raft.RaftGroupId group_id = 4; +} + +message XferReply { + required kbupd.ServiceId service = 1; +} + +message XferChunkRequest { + required SecretBytes data = 1; + required kbupd.PartitionKeyRangePB chunk_range = 2; + required kbupd.AttestationParameters min_attestation = 3; +} + +message XferChunkReply { + required kbupd.BackupId new_last = 1; + required uint32 chunk_size = 2; +} + +message XferErrorNotLeader { + optional bytes leader_node_id = 1; + required raft.TermId term = 2; +} diff --git a/enclave/kbupd_enclave/src/lib.rs b/enclave/kbupd_enclave/src/lib.rs new file mode 100644 index 0000000..2fb5b5f --- /dev/null +++ b/enclave/kbupd_enclave/src/lib.rs @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![crate_type = "staticlib"] +#![cfg_attr(not(any(test, feature = "test")), no_std)] +#![cfg_attr(not(any(test, feature = "test")), feature(alloc_error_handler))] +#![cfg_attr(not(any(test, feature = "test")), feature(thread_local))] +#![allow( + unused_parens, + clippy::style, + clippy::large_enum_variant, +)] +#![warn( + bare_trait_objects, + elided_lifetimes_in_paths, + trivial_numeric_casts, + variant_size_differences, + clippy::integer_arithmetic, + clippy::wildcard_enum_match_arm, +)] +#![deny( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::cast_sign_loss, + clippy::clone_on_ref_ptr, + clippy::expl_impl_clone_on_copy, + clippy::explicit_into_iter_loop, + clippy::explicit_iter_loop, + clippy::float_arithmetic, + clippy::float_cmp_const, + clippy::indexing_slicing, + clippy::maybe_infinite_iter, + clippy::mem_forget, + clippy::mut_mut, + clippy::needless_borrow, + clippy::option_unwrap_used, + clippy::panicking_unwrap, + clippy::print_stdout, + clippy::redundant_clone, + clippy::replace_consts, + clippy::result_unwrap_used, + clippy::shadow_unrelated, + clippy::unimplemented, + clippy::use_debug, + clippy::use_self, + clippy::use_underscore_binding, +)] + +extern crate alloc; + +#[cfg(not(any(test, feature = "test")))] +extern crate no_std_compat as std; + +#[cfg(not(any(test, feature = "test")))] +#[global_allocator] +static ALLOCATOR: allocator::System = allocator::System; + +#[macro_use] +mod macros; + +#[cfg(not(any(test, feature = "test")))] +mod allocator; +mod ffi; +mod hasher; +mod logging; +mod lru; +mod prelude; +#[allow(clippy::all, clippy::pedantic, clippy::integer_arithmetic)] +mod protobufs; +mod protobufs_impl; +mod service; +mod storage; +mod raft; +mod remote; +mod remote_group; +mod util; + +pub use crate::ffi::ecalls::{ + kbupd_send, + kbupd_send_flush, +}; + +pub mod external { + use sgx_ffi::sgx::{SgxStatus}; + use sgxsd_ffi::ecalls::{SgxsdServer}; + + use crate::service::main; + + #[no_mangle] + pub extern "C" fn sgxsd_enclave_server_init(p_args: *const ::InitArgs, + pp_state: *mut *mut main::SgxsdState) + -> SgxStatus + { + sgxsd_ffi::ecalls::sgxsd_enclave_server_init(p_args, pp_state) + } + + #[no_mangle] + pub extern "C" fn sgxsd_enclave_server_handle_call(p_args: *const ::HandleCallArgs, + msg_buf: sgxsd_ffi::ecalls::sgxsd_msg_buf_t, + mut from: sgxsd_ffi::ecalls::sgxsd_msg_from_t, + pp_state: *mut *mut main::SgxsdState) + -> SgxStatus + { + sgxsd_ffi::ecalls::sgxsd_enclave_server_handle_call(p_args, msg_buf, &mut from, pp_state) + } + + #[no_mangle] + pub extern "C" fn sgxsd_enclave_server_terminate(p_args: *const ::TerminateArgs, + p_state: *mut main::SgxsdState) + -> SgxStatus + { + sgxsd_ffi::ecalls::sgxsd_enclave_server_terminate(p_args, p_state) + } + + #[no_mangle] + pub extern "C" fn kbupd_enclave_recv_untrusted_msg(p_data: *const u8, data_size: usize) { + crate::service::main::whereis(|service_ref| { + let mut service = service_ref.borrow_mut(); + crate::ffi::ecalls::kbupd_enclave_recv_untrusted_msg(&mut *service, p_data, data_size) + }); + } +} diff --git a/enclave/kbupd_enclave/src/logging.rs b/enclave/kbupd_enclave/src/logging.rs new file mode 100644 index 0000000..df87c68 --- /dev/null +++ b/enclave/kbupd_enclave/src/logging.rs @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::sync::atomic::{AtomicBool, Ordering}; + +static VERBOSE: AtomicBool = AtomicBool::new(false); + +pub fn verbose_logging_enabled() -> bool { + VERBOSE.load(Ordering::Relaxed) +} + +pub fn set_verbose_logging_enabled(enabled: bool) { + VERBOSE.store(enabled, Ordering::SeqCst) +} diff --git a/enclave/kbupd_enclave/src/lru.rs b/enclave/kbupd_enclave/src/lru.rs new file mode 100644 index 0000000..e91f42b --- /dev/null +++ b/enclave/kbupd_enclave/src/lru.rs @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use intrusive_collections::*; +use intrusive_collections::{LinkedList}; + +use std::rc::*; + +pub struct Lru { + list: LinkedList>, + token: Rc, + length: usize, +} + +pub struct LruEntry { + item: T, + token: Weak, + link: LinkedListLink, +} + +intrusive_adapter!(pub LruAdapter = Rc>: LruEntry { link: LinkedListLink }); + +struct LruToken; + +impl Lru { + pub fn new() -> Self { + Self { + list: LinkedList::new(LruAdapter::new()), + token: Rc::new(LruToken), + length: Default::default(), + } + } + pub fn len(&self) -> usize { + self.length + } + pub fn push_back(&mut self, item: T) -> Weak> { + let lru_entry = Rc::new(LruEntry { + item, + token: Rc::downgrade(&self.token), + link: Default::default() + }); + let lru_entry_weak = Rc::downgrade(&lru_entry); + + self.list.push_back(lru_entry); + self.length = self.length.saturating_add(1); + lru_entry_weak + } + pub fn bump(&mut self, lru_entry_weak: &Weak>) -> bool { + if let Some(lru_entry) = lru_entry_weak.upgrade() { + // check that lru_entry is a member of self.list + if let Some(lru_entry_token) = lru_entry.token.upgrade() { + if !Rc::ptr_eq(&lru_entry_token, &self.token) { + return false; + } + } else { + return false; + } + if !lru_entry.link.is_linked() { + return false; + } + + // safety: lru_entry must be a member of self.list + let mut lru_cursor = unsafe { + self.list.cursor_mut_from_ptr(lru_entry.as_ref()) + }; + if let Some(lru_entry_rc) = lru_cursor.remove() { + self.list.push_back(lru_entry_rc); + } else { + self.list.push_back(lru_entry); + } + true + } else { + false + } + } + pub fn pop_front(&mut self) -> Option>> { + if let Some(lru_entry) = self.list.pop_front() { + self.length = self.length.saturating_sub(1); + Some(lru_entry) + } else { + None + } + } +} + +impl Default for Lru { + fn default() -> Self { + Self::new() + } +} + +impl LruEntry { + pub fn get(&self) -> &T { + &self.item + } +} + + +impl<'a, T> IntoIterator for &'a Lru { + type Item = &'a LruEntry; + type IntoIter = linked_list::Iter<'a, LruAdapter>; + + fn into_iter(self) -> linked_list::Iter<'a, LruAdapter> { + self.list.iter() + } +} diff --git a/enclave/kbupd_enclave/src/macros.rs b/enclave/kbupd_enclave/src/macros.rs new file mode 100644 index 0000000..e331b4e --- /dev/null +++ b/enclave/kbupd_enclave/src/macros.rs @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +macro_rules! error { + ($($arg:tt)*) => (log!($crate::protobufs::kbupd::EnclaveLogLevel::Error, $($arg)*)); +} +macro_rules! warn { + ($($arg:tt)*) => (log!($crate::protobufs::kbupd::EnclaveLogLevel::Warn, $($arg)*)); +} +macro_rules! info { + ($($arg:tt)*) => (log!($crate::protobufs::kbupd::EnclaveLogLevel::Info, $($arg)*)); +} +macro_rules! verbose { + ($($arg:tt)*) => ( + if $crate::logging::verbose_logging_enabled() { + log!($crate::protobufs::kbupd::EnclaveLogLevel::Info, $($arg)*) + } else { + #[cfg(feature = "debug")] + log!($crate::protobufs::kbupd::EnclaveLogLevel::Info, $($arg)*) + } + ); +} +macro_rules! debug { + ($($arg:tt)*) => ({ + #[cfg(feature = "debug")] + #[cfg(feature = "insecure")] + log!($crate::protobufs::kbupd::EnclaveLogLevel::Debug, $($arg)*) + }); +} + +macro_rules! log { + ($level:expr, $msg:expr) => ({ + log!($level, "{}", $msg) + }); + ($level:expr, $msg:expr,) => ({ + log!($level, $msg) + }); + ($level:expr, $fmt:expr, $($arg:tt)*) => ({ + $crate::kbupd_send($crate::protobufs::kbupd::EnclaveMessage { + inner: Some($crate::protobufs::kbupd::enclave_message::Inner::EnclaveLogSignal($crate::protobufs::kbupd::EnclaveLogSignal { + message: ::alloc::format!($fmt, $($arg)*).into_bytes(), + module: module_path!().as_bytes().to_vec(), + file: file!().rsplit("/").next().unwrap_or_default().as_bytes().to_vec(), + line: line!(), + level: $level.into(), + })), + }); + }); +} + +macro_rules! assert_true { + ($($arg:tt)*) => ({ + assert!($($arg)+); + true + }); +} + +macro_rules! assert_match { + ($pat:pat = $expr:expr) => ({ + if let $pat = $expr { + true + } else { + panic!("assertion failed: `$pat = $expr`") + } + }); + ($pat:pat = $expr:expr, $($arg:tt)*) => ({ + if let $pat = $expr { + true + } else { + panic!("assertion failed: `$pat = $expr`: {}", format_args!($($arg)+)) + } + }); +} + +macro_rules! static_unreachable { + () => ({ + #[cfg(not(debug_assertions))] + { + extern "C" { + pub fn __static_unreachable() -> !; + } + unsafe { __static_unreachable() }; + } + #[cfg(debug_assertions)] + unreachable!() + }) +} diff --git a/enclave/kbupd_enclave/src/prelude.rs b/enclave/kbupd_enclave/src/prelude.rs new file mode 100644 index 0000000..3872a2b --- /dev/null +++ b/enclave/kbupd_enclave/src/prelude.rs @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub use alloc::{format, vec}; +pub use alloc::borrow::{ToOwned}; +pub use alloc::boxed::{Box}; +pub use alloc::string::{String, ToString}; +pub use alloc::vec::{Vec}; diff --git a/enclave/kbupd_enclave/src/protobufs.proto b/enclave/kbupd_enclave/src/protobufs.proto new file mode 100644 index 0000000..d1f3169 --- /dev/null +++ b/enclave/kbupd_enclave/src/protobufs.proto @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +syntax = "proto2"; + +package protobufs; + +import "kbupd.proto"; +import "kbupd_client.proto"; +import "raft.proto"; +import "kbupd_enclave.proto"; diff --git a/enclave/kbupd_enclave/src/protobufs/kbupd.rs b/enclave/kbupd_enclave/src/protobufs/kbupd.rs new file mode 100644 index 0000000..6355313 --- /dev/null +++ b/enclave/kbupd_enclave/src/protobufs/kbupd.rs @@ -0,0 +1,722 @@ +// +// shared types +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ServiceId { + #[prost(bytes, required, tag="1")] + pub id: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BackupId { + #[prost(bytes, required, tag="1")] + pub id: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PartitionKeyRangePb { + #[prost(message, required, tag="1")] + pub first: BackupId, + #[prost(message, required, tag="2")] + pub last: BackupId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct IasReport { + #[prost(bytes, required, tag="2")] + pub body: std::vec::Vec, + #[prost(bytes, required, tag="3")] + pub signature: std::vec::Vec, + #[prost(bytes, repeated, tag="4")] + pub certificates: ::std::vec::Vec>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AttestationParameters { + #[prost(uint64, required, tag="1")] + pub unix_timestamp_seconds: u64, +} +// +// transaction requests +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CreateBackupRequest { + #[prost(message, required, tag="1")] + pub backup_id: BackupId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CreateBackupReply { + #[prost(bytes, required, tag="2")] + pub nonce: std::vec::Vec, + #[prost(uint32, optional, tag="3")] + pub tries: ::std::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteBackupRequest { + #[prost(message, required, tag="1")] + pub backup_id: BackupId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteBackupReply { +} +// +// untrusted messages +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UntrustedMessageBatch { + #[prost(message, repeated, tag="1")] + pub messages: ::std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UntrustedMessage { + #[prost(oneof="untrusted_message::Inner", tags="1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16")] + pub inner: ::std::option::Option, +} +pub mod untrusted_message { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Inner { + #[prost(message, tag="1")] + StartFrontendRequest(super::StartFrontendRequest), + #[prost(message, tag="2")] + StartReplicaRequest(super::StartReplicaRequest), + #[prost(message, tag="3")] + StartReplicaGroupRequest(super::StartReplicaGroupRequest), + #[prost(message, tag="4")] + UntrustedTransactionRequest(super::UntrustedTransactionRequest), + #[prost(message, tag="5")] + UntrustedXferRequest(super::UntrustedXferRequest), + #[prost(message, tag="6")] + GetEnclaveStatusRequest(super::GetEnclaveStatusRequest), + #[prost(message, tag="8")] + GetQeInfoReply(super::GetQeInfoReply), + #[prost(message, tag="9")] + GetQuoteReply(super::GetQuoteReply), + #[prost(message, tag="10")] + GetAttestationReply(super::GetAttestationReply), + #[prost(message, tag="11")] + NewMessageSignal(super::NewMessageSignal), + #[prost(message, tag="12")] + TimerTickSignal(super::TimerTickSignal), + #[prost(message, tag="13")] + SetFrontendConfigSignal(super::SetFrontendConfigSignal), + #[prost(message, tag="14")] + SetReplicaConfigSignal(super::SetReplicaConfigSignal), + #[prost(message, tag="15")] + ResetPeerSignal(super::ResetPeerSignal), + #[prost(message, tag="16")] + SetVerboseLoggingSignal(super::SetVerboseLoggingSignal), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PartitionConfig { + #[prost(bytes, required, tag="1")] + pub group_id: std::vec::Vec, + #[prost(message, optional, tag="2")] + pub range: ::std::option::Option, + #[prost(bytes, repeated, tag="3")] + pub node_ids: ::std::vec::Vec>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StartFrontendRequest { + #[prost(message, repeated, tag="1")] + pub partitions: ::std::vec::Vec, + #[prost(message, required, tag="2")] + pub config: EnclaveFrontendConfig, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveFrontendConfig { + #[prost(uint32, required, tag="1")] + pub replica_timeout_ticks: u32, + #[prost(uint32, required, tag="2")] + pub request_quote_ticks: u32, + #[prost(uint32, required, tag="3")] + pub min_connect_timeout_ticks: u32, + #[prost(uint32, required, tag="4")] + pub max_connect_timeout_ticks: u32, + #[prost(uint32, required, tag="5")] + pub pending_request_count: u32, + #[prost(uint32, required, tag="6")] + pub pending_request_ttl: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SourcePartitionConfig { + #[prost(message, required, tag="1")] + pub range: PartitionKeyRangePb, + #[prost(bytes, repeated, tag="2")] + pub node_ids: ::std::vec::Vec>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StartReplicaRequest { + #[prost(message, required, tag="1")] + pub config: EnclaveReplicaConfig, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveReplicaConfig { + #[prost(uint32, required, tag="1")] + pub election_timeout_ticks: u32, + #[prost(uint32, required, tag="2")] + pub heartbeat_timeout_ticks: u32, + #[prost(uint32, required, tag="3")] + pub request_quote_ticks: u32, + #[prost(uint32, required, tag="4")] + pub min_connect_timeout_ticks: u32, + #[prost(uint32, required, tag="5")] + pub max_connect_timeout_ticks: u32, + #[prost(uint32, required, tag="6")] + pub attestation_expiry_ticks: u32, + #[prost(uint32, required, tag="7")] + pub replication_chunk_size: u32, + #[prost(uint32, required, tag="8")] + pub transfer_chunk_size: u32, + #[prost(uint32, required, tag="10")] + pub storage_page_cache_size: u32, + #[prost(uint32, required, tag="13")] + pub raft_log_index_page_cache_size: u32, + #[prost(uint32, required, tag="14")] + pub max_frontend_count: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StartReplicaGroupRequest { + #[prost(bytes, repeated, tag="1")] + pub peer_node_ids: ::std::vec::Vec>, + #[prost(message, required, tag="2")] + pub config: EnclaveReplicaGroupConfig, + #[prost(message, optional, tag="3")] + pub source_partition: ::std::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveReplicaGroupConfig { + #[prost(uint32, required, tag="1")] + pub storage_size: u32, + #[prost(uint64, required, tag="2")] + pub raft_log_data_size: u64, + #[prost(uint32, required, tag="3")] + pub raft_log_index_size: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NewMessageSignal { + #[prost(bytes, required, tag="1")] + pub node_id: std::vec::Vec, + #[prost(bytes, required, tag="2")] + pub data: std::vec::Vec, + #[prost(bool, required, tag="3")] + pub syn: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TimerTickSignal { + #[prost(fixed64, required, tag="1")] + pub now_secs: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetFrontendConfigSignal { + #[prost(message, required, tag="1")] + pub config: EnclaveFrontendConfig, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetReplicaConfigSignal { + #[prost(message, required, tag="1")] + pub config: EnclaveReplicaConfig, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResetPeerSignal { + #[prost(bytes, required, tag="1")] + pub peer_node_id: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetVerboseLoggingSignal { + #[prost(bool, required, tag="1")] + pub verbose_logging: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetQeInfoReply { + #[prost(bytes, required, tag="1")] + pub mrenclave: std::vec::Vec, + #[prost(uint64, required, tag="2")] + pub flags: u64, + #[prost(uint64, required, tag="3")] + pub xfrm: u64, + #[prost(uint32, required, tag="4")] + pub misc_select: u32, + #[prost(uint32, required, tag="5")] + pub config_svn: u32, + #[prost(bytes, required, tag="6")] + pub config_id: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetQuoteReply { + #[prost(bytes, required, tag="1")] + pub request_id: std::vec::Vec, + #[prost(bytes, required, tag="2")] + pub sgx_quote: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetAttestationReply { + #[prost(bytes, required, tag="1")] + pub request_id: std::vec::Vec, + #[prost(message, required, tag="2")] + pub ias_report: IasReport, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UntrustedTransactionRequest { + #[prost(uint64, required, tag="1")] + pub request_id: u64, + #[prost(oneof="untrusted_transaction_request::Data", tags="2, 3")] + pub data: ::std::option::Option, +} +pub mod untrusted_transaction_request { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Data { + #[prost(message, tag="2")] + CreateBackupRequest(super::CreateBackupRequest), + #[prost(message, tag="3")] + DeleteBackupRequest(super::DeleteBackupRequest), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UntrustedXferRequest { + #[prost(uint64, required, tag="1")] + pub request_id: u64, + #[prost(oneof="untrusted_xfer_request::Data", tags="2")] + pub data: ::std::option::Option, +} +pub mod untrusted_xfer_request { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Data { + #[prost(enumeration="super::XferControlCommand", tag="2")] + XferControlCommand(i32), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetEnclaveStatusRequest { + #[prost(bool, required, tag="1")] + pub memory_status: bool, +} +// +// enclave messages +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveMessageBatch { + #[prost(message, repeated, tag="1")] + pub messages: ::std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveMessage { + #[prost(oneof="enclave_message::Inner", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12")] + pub inner: ::std::option::Option, +} +pub mod enclave_message { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Inner { + #[prost(message, tag="1")] + StartFrontendReply(super::StartFrontendReply), + #[prost(message, tag="2")] + StartReplicaReply(super::StartReplicaReply), + #[prost(message, tag="3")] + StartReplicaGroupReply(super::StartReplicaGroupReply), + #[prost(message, tag="4")] + UntrustedTransactionReply(super::UntrustedTransactionReply), + #[prost(message, tag="5")] + UntrustedXferReply(super::UntrustedXferReply), + #[prost(message, tag="6")] + GetEnclaveStatusReply(super::GetEnclaveStatusReply), + #[prost(message, tag="7")] + SendMessageRequest(super::SendMessageRequest), + #[prost(message, tag="8")] + GetQeInfoRequest(super::GetQeInfoRequest), + #[prost(message, tag="9")] + GetQuoteRequest(super::GetQuoteRequest), + #[prost(message, tag="10")] + GetAttestationRequest(super::GetAttestationRequest), + #[prost(message, tag="11")] + EnclaveLogSignal(super::EnclaveLogSignal), + #[prost(message, tag="12")] + EnclaveTransactionSignal(super::EnclaveTransactionSignal), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StartFrontendReply { + #[prost(bytes, required, tag="1")] + pub node_id: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StartReplicaReply { + #[prost(bytes, required, tag="1")] + pub node_id: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StartReplicaGroupReply { + #[prost(message, optional, tag="1")] + pub service_id: ::std::option::Option, + #[prost(bytes, optional, tag="2")] + pub group_id: ::std::option::Option>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetEnclaveStatusReply { + #[prost(oneof="get_enclave_status_reply::Inner", tags="1, 2")] + pub inner: ::std::option::Option, +} +pub mod get_enclave_status_reply { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Inner { + #[prost(message, tag="1")] + ReplicaStatus(super::EnclaveReplicaStatus), + #[prost(message, tag="2")] + FrontendStatus(super::EnclaveFrontendStatus), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SendMessageRequest { + #[prost(bytes, required, tag="1")] + pub node_id: std::vec::Vec, + #[prost(bytes, required, tag="2")] + pub data: std::vec::Vec, + #[prost(bool, required, tag="3")] + pub syn: bool, + #[prost(bytes, optional, tag="4")] + pub debug_msg: ::std::option::Option>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetQeInfoRequest { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetQuoteRequest { + #[prost(bytes, required, tag="1")] + pub request_id: std::vec::Vec, + #[prost(bytes, required, tag="2")] + pub sgx_report: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetAttestationRequest { + #[prost(bytes, required, tag="1")] + pub request_id: std::vec::Vec, + #[prost(bytes, required, tag="2")] + pub sgx_quote: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UntrustedTransactionReply { + #[prost(uint64, required, tag="1")] + pub request_id: u64, + #[prost(oneof="untrusted_transaction_reply::Data", tags="2, 3")] + pub data: ::std::option::Option, +} +pub mod untrusted_transaction_reply { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Data { + #[prost(message, tag="2")] + CreateBackupReply(super::CreateBackupReply), + #[prost(message, tag="3")] + DeleteBackupReply(super::DeleteBackupReply), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UntrustedXferReply { + #[prost(uint64, required, tag="1")] + pub request_id: u64, + #[prost(enumeration="UntrustedXferReplyStatus", required, tag="2")] + pub status: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveLogSignal { + #[prost(bytes, required, tag="1")] + pub message: std::vec::Vec, + #[prost(bytes, required, tag="2")] + pub module: std::vec::Vec, + #[prost(bytes, required, tag="3")] + pub file: std::vec::Vec, + #[prost(uint32, required, tag="4")] + pub line: u32, + #[prost(enumeration="EnclaveLogLevel", required, tag="5")] + pub level: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveTransactionSignal { + #[prost(uint64, required, tag="1")] + pub log_index: u64, + #[prost(oneof="enclave_transaction_signal::Transaction", tags="2, 3, 4, 5, 6, 7, 8, 9, 10")] + pub transaction: ::std::option::Option, +} +pub mod enclave_transaction_signal { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Transaction { + #[prost(message, tag="2")] + FrontendRequest(super::EnclaveFrontendRequestTransaction), + #[prost(message, tag="3")] + StartXfer(super::EnclaveStartXferTransaction), + #[prost(message, tag="4")] + SetSid(super::EnclaveSetSidTransaction), + #[prost(message, tag="5")] + RemoveChunk(super::EnclaveRemoveChunkTransaction), + #[prost(message, tag="6")] + ApplyChunk(super::EnclaveApplyChunkTransaction), + #[prost(message, tag="7")] + PauseXfer(super::EnclavePauseXferTransaction), + #[prost(message, tag="8")] + ResumeXfer(super::EnclaveResumeXferTransaction), + #[prost(message, tag="9")] + FinishXfer(super::EnclaveFinishXferTransaction), + #[prost(message, tag="10")] + SetTime(super::EnclaveSetTimeTransaction), + } +} +// +// enclave transactions +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveFrontendRequestTransaction { + #[prost(oneof="enclave_frontend_request_transaction::Transaction", tags="1, 2, 3, 4, 5, 6, 7, 8")] + pub transaction: ::std::option::Option, +} +pub mod enclave_frontend_request_transaction { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Transaction { + #[prost(message, tag="1")] + Create(super::EnclaveCreateBackupTransaction), + #[prost(message, tag="2")] + Backup(super::EnclaveBackupTransaction), + #[prost(message, tag="3")] + Restore(super::EnclaveRestoreTransaction), + #[prost(message, tag="4")] + Delete(super::EnclaveDeleteBackupTransaction), + #[prost(message, tag="5")] + XferInProgress(super::EnclaveTransactionErrorXferInProgress), + #[prost(message, tag="6")] + WrongPartition(super::EnclaveTransactionErrorWrongPartition), + #[prost(message, tag="7")] + InvalidRequest(super::EnclaveTransactionErrorInvalidRequest), + #[prost(message, tag="8")] + InternalError(super::EnclaveTransactionErrorInternalError), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveCreateBackupTransaction { + #[prost(message, required, tag="1")] + pub backup_id: BackupId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveBackupTransaction { + #[prost(message, required, tag="1")] + pub backup_id: BackupId, + #[prost(enumeration="super::kbupd_client::backup_response::Status", required, tag="2")] + pub status: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveRestoreTransaction { + #[prost(message, required, tag="1")] + pub backup_id: BackupId, + #[prost(enumeration="super::kbupd_client::restore_response::Status", required, tag="2")] + pub status: i32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveDeleteBackupTransaction { + #[prost(message, required, tag="1")] + pub backup_id: BackupId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveTransactionErrorXferInProgress { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveTransactionErrorWrongPartition { + #[prost(bool, required, tag="1")] + pub new_partition_unknown: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveTransactionErrorInvalidRequest { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveTransactionErrorInternalError { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveStartXferTransaction { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveSetSidTransaction { + #[prost(message, optional, tag="1")] + pub service_id: ::std::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveRemoveChunkTransaction { + #[prost(message, optional, tag="1")] + pub chunk_range: ::std::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveApplyChunkTransaction { + #[prost(message, optional, tag="1")] + pub chunk_range: ::std::option::Option, + #[prost(message, repeated, tag="2")] + pub chunk_ids: ::std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclavePauseXferTransaction { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveResumeXferTransaction { + #[prost(message, optional, tag="1")] + pub chunk_range: ::std::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveFinishXferTransaction { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveSetTimeTransaction { + #[prost(uint64, optional, tag="1")] + pub now_secs: ::std::option::Option, +} +// +// enclave status +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveMemoryStatus { + #[prost(uint32, required, tag="1")] + pub footprint_bytes: u32, + #[prost(uint32, required, tag="2")] + pub used_bytes: u32, + #[prost(uint32, required, tag="3")] + pub free_chunks: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveReplicaStatus { + #[prost(message, optional, tag="1")] + pub memory_status: ::std::option::Option, + #[prost(message, optional, tag="2")] + pub partition: ::std::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveReplicaPartitionStatus { + #[prost(bytes, required, tag="1")] + pub group_id: std::vec::Vec, + #[prost(bytes, optional, tag="2")] + pub service_id: ::std::option::Option>, + #[prost(message, optional, tag="3")] + pub range: ::std::option::Option, + #[prost(message, repeated, tag="4")] + pub peers: ::std::vec::Vec, + #[prost(message, required, tag="5")] + pub min_attestation: AttestationParameters, + #[prost(bool, required, tag="6")] + pub is_leader: bool, + #[prost(uint64, required, tag="7")] + pub current_term: u64, + #[prost(uint64, required, tag="8")] + pub prev_log_index: u64, + #[prost(uint64, required, tag="9")] + pub last_applied_index: u64, + #[prost(uint64, required, tag="10")] + pub commit_index: u64, + #[prost(uint64, required, tag="11")] + pub last_log_index: u64, + #[prost(uint64, required, tag="12")] + pub last_log_term: u64, + #[prost(uint64, required, tag="13")] + pub log_data_length: u64, + #[prost(uint64, required, tag="14")] + pub backup_count: u64, + #[prost(oneof="enclave_replica_partition_status::XferStatus", tags="15, 16")] + pub xfer_status: ::std::option::Option, +} +pub mod enclave_replica_partition_status { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum XferStatus { + #[prost(message, tag="15")] + IncomingXferStatus(super::EnclaveIncomingXferStatus), + #[prost(message, tag="16")] + OutgoingXferStatus(super::EnclaveOutgoingXferStatus), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclavePeerStatus { + #[prost(bytes, required, tag="1")] + pub node_id: std::vec::Vec, + #[prost(message, optional, tag="2")] + pub attestation: ::std::option::Option, + #[prost(message, optional, tag="3")] + pub replication_status: ::std::option::Option, + #[prost(bool, required, tag="4")] + pub is_leader: bool, + #[prost(uint64, required, tag="5")] + pub inflight_requests: u64, + #[prost(uint64, required, tag="6")] + pub unsent_requests: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclavePeerReplicationStatus { + #[prost(uint64, required, tag="1")] + pub next_index: u64, + #[prost(uint64, required, tag="2")] + pub match_index: u64, + #[prost(uint64, optional, tag="3")] + pub inflight_index: ::std::option::Option, + #[prost(bool, required, tag="4")] + pub probing: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveIncomingXferStatus { + #[prost(message, required, tag="1")] + pub desired_range: PartitionKeyRangePb, + #[prost(message, repeated, tag="2")] + pub nodes: ::std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveOutgoingXferStatus { + #[prost(bytes, required, tag="1")] + pub group_id: std::vec::Vec, + #[prost(message, required, tag="2")] + pub full_xfer_range: PartitionKeyRangePb, + #[prost(message, optional, tag="3")] + pub current_chunk_range: ::std::option::Option, + #[prost(bool, required, tag="4")] + pub paused: bool, + #[prost(message, optional, tag="5")] + pub min_attestation: ::std::option::Option, + #[prost(message, repeated, tag="6")] + pub nodes: ::std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveFrontendStatus { + #[prost(message, optional, tag="1")] + pub memory_status: ::std::option::Option, + #[prost(message, repeated, tag="2")] + pub partitions: ::std::vec::Vec, + #[prost(message, repeated, tag="3")] + pub ranges: ::std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveFrontendPartitionStatus { + #[prost(bytes, required, tag="1")] + pub group_id: std::vec::Vec, + #[prost(message, repeated, tag="2")] + pub nodes: ::std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveFrontendRangeStatus { + #[prost(message, required, tag="1")] + pub range: PartitionKeyRangePb, + #[prost(bytes, required, tag="2")] + pub group_id: std::vec::Vec, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum XferControlCommand { + Start = 1, + Finish = 2, + Cancel = 3, + Pause = 4, + Resume = 5, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum UntrustedXferReplyStatus { + Unknown = 0, + Ok = 1, + NotLeader = 2, + InvalidState = 3, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum EnclaveLogLevel { + Error = 0, + Warn = 1, + Info = 2, + Debug = 3, +} diff --git a/enclave/kbupd_enclave/src/protobufs/kbupd_client.rs b/enclave/kbupd_enclave/src/protobufs/kbupd_client.rs new file mode 100644 index 0000000..414fcdf --- /dev/null +++ b/enclave/kbupd_enclave/src/protobufs/kbupd_client.rs @@ -0,0 +1,108 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Request { + #[prost(message, optional, tag="1")] + pub backup: ::std::option::Option, + #[prost(message, optional, tag="2")] + pub restore: ::std::option::Option, + #[prost(message, optional, tag="3")] + pub delete: ::std::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Response { + #[prost(message, optional, tag="1")] + pub backup: ::std::option::Option, + #[prost(message, optional, tag="2")] + pub restore: ::std::option::Option, + #[prost(message, optional, tag="3")] + pub delete: ::std::option::Option, +} +// +// backup +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BackupRequest { + #[prost(bytes, optional, tag="1")] + pub service_id: ::std::option::Option>, + #[prost(bytes, optional, tag="2")] + pub backup_id: ::std::option::Option>, + #[prost(bytes, optional, tag="3")] + pub nonce: ::std::option::Option>, + #[prost(uint64, optional, tag="4")] + pub valid_from: ::std::option::Option, + #[prost(bytes, optional, tag="5")] + pub data: ::std::option::Option>, + #[prost(bytes, optional, tag="6")] + pub pin: ::std::option::Option>, + #[prost(uint32, optional, tag="7")] + pub tries: ::std::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BackupResponse { + #[prost(enumeration="backup_response::Status", optional, tag="1")] + pub status: ::std::option::Option, + #[prost(bytes, optional, tag="2")] + pub nonce: ::std::option::Option>, +} +pub mod backup_response { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum Status { + Ok = 1, + AlreadyExists = 2, + NotYetValid = 3, + } +} +// +// restore +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RestoreRequest { + #[prost(bytes, optional, tag="1")] + pub service_id: ::std::option::Option>, + #[prost(bytes, optional, tag="2")] + pub backup_id: ::std::option::Option>, + #[prost(bytes, optional, tag="3")] + pub nonce: ::std::option::Option>, + #[prost(uint64, optional, tag="4")] + pub valid_from: ::std::option::Option, + #[prost(bytes, optional, tag="5")] + pub pin: ::std::option::Option>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RestoreResponse { + #[prost(enumeration="restore_response::Status", optional, tag="1")] + pub status: ::std::option::Option, + #[prost(bytes, optional, tag="2")] + pub nonce: ::std::option::Option>, + #[prost(bytes, optional, tag="3")] + pub data: ::std::option::Option>, + #[prost(uint32, optional, tag="4")] + pub tries: ::std::option::Option, +} +pub mod restore_response { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum Status { + Ok = 1, + NonceMismatch = 2, + NotYetValid = 3, + Missing = 4, + PinMismatch = 5, + } +} +// +// delete +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteRequest { + #[prost(bytes, optional, tag="1")] + pub service_id: ::std::option::Option>, + #[prost(bytes, optional, tag="2")] + pub backup_id: ::std::option::Option>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteResponse { +} diff --git a/enclave/kbupd_enclave/src/protobufs/kbupd_enclave.rs b/enclave/kbupd_enclave/src/protobufs/kbupd_enclave.rs new file mode 100644 index 0000000..8ad72b2 --- /dev/null +++ b/enclave/kbupd_enclave/src/protobufs/kbupd_enclave.rs @@ -0,0 +1,431 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SecretBytes { + #[prost(bytes, required, tag="1")] + pub data: std::vec::Vec, +} +// +// transactions +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionData { + #[prost(oneof="transaction_data::Inner", tags="1, 2, 3, 4, 5, 6, 7, 8, 9")] + pub inner: ::std::option::Option, +} +pub mod transaction_data { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Inner { + #[prost(message, tag="1")] + FrontendRequest(super::FrontendRequestTransaction), + #[prost(message, tag="2")] + StartXfer(super::StartXferTransaction), + #[prost(message, tag="3")] + SetSid(super::SetSidTransaction), + #[prost(message, tag="4")] + RemoveChunk(super::RemoveChunkTransaction), + #[prost(message, tag="5")] + ApplyChunk(super::ApplyChunkTransaction), + #[prost(message, tag="6")] + PauseXfer(super::PauseXferTransaction), + #[prost(message, tag="7")] + ResumeXfer(super::ResumeXferTransaction), + #[prost(message, tag="8")] + FinishXfer(super::FinishXferTransaction), + #[prost(message, tag="9")] + SetTime(super::SetTimeTransaction), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FrontendRequestTransaction { + #[prost(bytes, required, tag="1")] + pub from_node_id: std::vec::Vec, + #[prost(uint64, required, tag="2")] + pub request_id: u64, + #[prost(oneof="frontend_request_transaction::Transaction", tags="3, 4, 5, 6")] + pub transaction: ::std::option::Option, +} +pub mod frontend_request_transaction { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Transaction { + #[prost(message, tag="3")] + Create(super::CreateBackupTransaction), + #[prost(message, tag="4")] + Backup(super::BackupTransaction), + #[prost(message, tag="5")] + Restore(super::RestoreTransaction), + #[prost(message, tag="6")] + Delete(super::DeleteBackupTransaction), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CreateBackupTransaction { + #[prost(message, required, tag="1")] + pub backup_id: super::kbupd::BackupId, + #[prost(bytes, required, tag="2")] + pub new_creation_nonce: std::vec::Vec, + #[prost(bytes, required, tag="3")] + pub new_nonce: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BackupTransaction { + #[prost(message, required, tag="1")] + pub backup_id: super::kbupd::BackupId, + #[prost(bytes, required, tag="2")] + pub old_nonce: std::vec::Vec, + #[prost(bytes, required, tag="3")] + pub new_creation_nonce: std::vec::Vec, + #[prost(bytes, required, tag="4")] + pub new_nonce: std::vec::Vec, + #[prost(message, required, tag="5")] + pub data: SecretBytes, + #[prost(message, required, tag="6")] + pub pin: SecretBytes, + #[prost(uint32, required, tag="7")] + pub tries: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RestoreTransaction { + #[prost(message, required, tag="1")] + pub backup_id: super::kbupd::BackupId, + #[prost(bytes, required, tag="2")] + pub creation_nonce: std::vec::Vec, + #[prost(bytes, required, tag="3")] + pub old_nonce: std::vec::Vec, + #[prost(bytes, required, tag="4")] + pub new_nonce: std::vec::Vec, + #[prost(message, required, tag="5")] + pub pin: SecretBytes, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteBackupTransaction { + #[prost(message, required, tag="1")] + pub backup_id: super::kbupd::BackupId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StartXferTransaction { + #[prost(bytes, required, tag="1")] + pub from_node_id: std::vec::Vec, + #[prost(message, required, tag="2")] + pub xfer_request: XferRequest, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetSidTransaction { + #[prost(bytes, required, tag="1")] + pub from_node_id: std::vec::Vec, + #[prost(message, required, tag="2")] + pub service_id: super::kbupd::ServiceId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RemoveChunkTransaction { + #[prost(bytes, required, tag="1")] + pub from_node_id: std::vec::Vec, + #[prost(message, required, tag="2")] + pub xfer_chunk_reply: XferChunkReply, + #[prost(message, required, tag="3")] + pub chunk_last: super::kbupd::BackupId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApplyChunkTransaction { + #[prost(bytes, required, tag="1")] + pub from_node_id: std::vec::Vec, + #[prost(message, required, tag="2")] + pub xfer_chunk_request: XferChunkRequest, + #[prost(message, required, tag="3")] + pub xfer_chunk_reply: XferChunkReply, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PauseXferTransaction { + #[prost(uint64, required, tag="1")] + pub request_id: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResumeXferTransaction { + #[prost(uint64, required, tag="1")] + pub request_id: u64, + #[prost(message, required, tag="2")] + pub chunk_last: super::kbupd::BackupId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FinishXferTransaction { + #[prost(uint64, required, tag="1")] + pub request_id: u64, + #[prost(bool, required, tag="2")] + pub force: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SetTimeTransaction { + #[prost(uint64, required, tag="1")] + pub now_secs: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PeerConnectRequest { + #[prost(enumeration="NodeType", required, tag="1")] + pub node_type: i32, + #[prost(message, optional, tag="2")] + pub ias_report: ::std::option::Option, + #[prost(bytes, required, tag="3")] + pub noise_data: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PeerConnectReply { + #[prost(bytes, required, tag="1")] + pub sgx_quote: std::vec::Vec, + #[prost(bytes, required, tag="2")] + pub noise_data: std::vec::Vec, +} +// +// enclave-to-enclave requests +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveGetQuoteRequest { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EnclaveGetQuoteReply { + #[prost(bytes, required, tag="1")] + pub sgx_quote: std::vec::Vec, +} +// +// frontend to replica +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FrontendToReplicaMessage { + #[prost(oneof="frontend_to_replica_message::Inner", tags="1, 2")] + pub inner: ::std::option::Option, +} +pub mod frontend_to_replica_message { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Inner { + #[prost(message, tag="1")] + TransactionRequest(super::TransactionRequest), + #[prost(message, tag="2")] + EnclaveGetQuoteRequest(super::EnclaveGetQuoteRequest), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionRequest { + #[prost(uint64, required, tag="1")] + pub request_id: u64, + #[prost(oneof="transaction_request::Data", tags="2, 3, 4, 5")] + pub data: ::std::option::Option, +} +pub mod transaction_request { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Data { + #[prost(message, tag="2")] + Create(super::super::kbupd::CreateBackupRequest), + #[prost(message, tag="3")] + Backup(super::BackupTransactionRequest), + #[prost(message, tag="4")] + Restore(super::RestoreTransactionRequest), + #[prost(message, tag="5")] + Delete(super::DeleteTransactionRequest), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BackupTransactionRequest { + #[prost(bytes, optional, tag="1")] + pub service_id: ::std::option::Option>, + #[prost(message, required, tag="2")] + pub backup_id: super::kbupd::BackupId, + #[prost(bytes, required, tag="3")] + pub nonce: std::vec::Vec, + #[prost(uint64, required, tag="4")] + pub valid_from: u64, + #[prost(message, required, tag="5")] + pub data: SecretBytes, + #[prost(message, required, tag="6")] + pub pin: SecretBytes, + #[prost(uint32, required, tag="7")] + pub tries: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RestoreTransactionRequest { + #[prost(bytes, optional, tag="1")] + pub service_id: ::std::option::Option>, + #[prost(message, required, tag="2")] + pub backup_id: super::kbupd::BackupId, + #[prost(bytes, required, tag="3")] + pub nonce: std::vec::Vec, + #[prost(uint64, required, tag="4")] + pub valid_from: u64, + #[prost(message, required, tag="5")] + pub pin: SecretBytes, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeleteTransactionRequest { + #[prost(bytes, optional, tag="1")] + pub service_id: ::std::option::Option>, + #[prost(message, required, tag="2")] + pub backup_id: super::kbupd::BackupId, +} +// +// replica to frontend +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReplicaToFrontendMessage { + #[prost(oneof="replica_to_frontend_message::Inner", tags="1, 2")] + pub inner: ::std::option::Option, +} +pub mod replica_to_frontend_message { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Inner { + #[prost(message, tag="1")] + TransactionReply(super::TransactionReply), + #[prost(message, tag="2")] + EnclaveGetQuoteReply(super::EnclaveGetQuoteReply), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionReply { + #[prost(uint64, required, tag="1")] + pub request_id: u64, + #[prost(oneof="transaction_reply::Data", tags="2, 3, 4, 5, 6, 7, 8, 9, 10")] + pub data: ::std::option::Option, +} +pub mod transaction_reply { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Data { + #[prost(message, tag="2")] + ClientResponse(super::super::kbupd_client::Response), + #[prost(message, tag="3")] + CreateBackupReply(super::super::kbupd::CreateBackupReply), + #[prost(message, tag="4")] + DeleteBackupReply(super::super::kbupd::DeleteBackupReply), + #[prost(message, tag="5")] + NotLeader(super::TransactionErrorNotLeader), + #[prost(message, tag="6")] + WrongPartition(super::TransactionErrorWrongPartition), + #[prost(message, tag="7")] + ServiceIdMismatch(super::TransactionErrorServiceIdMismatch), + #[prost(message, tag="8")] + XferInProgress(super::TransactionErrorXferInProgress), + #[prost(message, tag="9")] + InvalidRequest(super::TransactionErrorInvalidRequest), + #[prost(message, tag="10")] + InternalError(super::TransactionErrorInternalError), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionErrorNotLeader { + #[prost(bytes, optional, tag="1")] + pub leader_node_id: ::std::option::Option>, + #[prost(message, required, tag="2")] + pub term: super::raft::TermId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionErrorWrongPartition { + #[prost(message, optional, tag="1")] + pub range: ::std::option::Option, + #[prost(message, optional, tag="2")] + pub new_partition: ::std::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionErrorServiceIdMismatch { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionErrorXferInProgress { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionErrorInvalidRequest { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionErrorInternalError { +} +// +// replica to replica +// + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ReplicaToReplicaMessage { + #[prost(oneof="replica_to_replica_message::Inner", tags="1, 2, 8, 9, 3, 4, 5, 6, 7")] + pub inner: ::std::option::Option, +} +pub mod replica_to_replica_message { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Inner { + #[prost(message, tag="1")] + RaftMessage(super::super::raft::RaftMessage), + #[prost(message, tag="2")] + CreateRaftGroupRequest(super::CreateRaftGroupRequest), + #[prost(message, tag="8")] + EnclaveGetQuoteRequest(super::EnclaveGetQuoteRequest), + #[prost(message, tag="9")] + EnclaveGetQuoteReply(super::EnclaveGetQuoteReply), + #[prost(message, tag="3")] + XferRequest(super::XferRequest), + #[prost(message, tag="4")] + XferReply(super::XferReply), + #[prost(message, tag="5")] + XferChunkRequest(super::XferChunkRequest), + #[prost(message, tag="6")] + XferChunkReply(super::XferChunkReply), + #[prost(message, tag="7")] + XferErrorNotLeader(super::XferErrorNotLeader), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CreateRaftGroupRequest { + #[prost(message, optional, tag="1")] + pub service_id: ::std::option::Option, + #[prost(message, required, tag="2")] + pub group_id: super::raft::RaftGroupId, + #[prost(bytes, repeated, tag="3")] + pub node_ids: ::std::vec::Vec>, + #[prost(message, required, tag="4")] + pub config: super::kbupd::EnclaveReplicaGroupConfig, + #[prost(message, optional, tag="5")] + pub source_partition: ::std::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct XferRequest { + #[prost(uint32, required, tag="1")] + pub chunk_size: u32, + #[prost(message, required, tag="2")] + pub full_range: super::kbupd::PartitionKeyRangePb, + #[prost(bytes, repeated, tag="3")] + pub node_ids: ::std::vec::Vec>, + #[prost(message, required, tag="4")] + pub group_id: super::raft::RaftGroupId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct XferReply { + #[prost(message, required, tag="1")] + pub service: super::kbupd::ServiceId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct XferChunkRequest { + #[prost(message, required, tag="1")] + pub data: SecretBytes, + #[prost(message, required, tag="2")] + pub chunk_range: super::kbupd::PartitionKeyRangePb, + #[prost(message, required, tag="3")] + pub min_attestation: super::kbupd::AttestationParameters, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct XferChunkReply { + #[prost(message, required, tag="1")] + pub new_last: super::kbupd::BackupId, + #[prost(uint32, required, tag="2")] + pub chunk_size: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct XferErrorNotLeader { + #[prost(bytes, optional, tag="1")] + pub leader_node_id: ::std::option::Option>, + #[prost(message, required, tag="2")] + pub term: super::raft::TermId, +} +// +// remote enclave handshake +// + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum NodeType { + None = 0, + Frontend = 1, + Replica = 2, +} diff --git a/enclave/kbupd_enclave/src/protobufs/mod.rs b/enclave/kbupd_enclave/src/protobufs/mod.rs new file mode 100644 index 0000000..f1d362d --- /dev/null +++ b/enclave/kbupd_enclave/src/protobufs/mod.rs @@ -0,0 +1,4 @@ +pub mod kbupd; +pub mod kbupd_client; +pub mod kbupd_enclave; +pub mod raft; diff --git a/enclave/kbupd_enclave/src/protobufs/raft.rs b/enclave/kbupd_enclave/src/protobufs/raft.rs new file mode 100644 index 0000000..4bde5c0 --- /dev/null +++ b/enclave/kbupd_enclave/src/protobufs/raft.rs @@ -0,0 +1,76 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RaftMessage { + #[prost(message, required, tag="1")] + pub group: RaftGroupId, + #[prost(message, required, tag="2")] + pub term: TermId, + #[prost(oneof="raft_message::Inner", tags="3, 4, 5, 6")] + pub inner: ::std::option::Option, +} +pub mod raft_message { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Inner { + #[prost(message, tag="3")] + VoteRequest(super::VoteRequest), + #[prost(message, tag="4")] + VoteResponse(super::VoteResponse), + #[prost(message, tag="5")] + AppendRequest(super::AppendRequest), + #[prost(message, tag="6")] + AppendResponse(super::AppendResponse), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VoteRequest { + #[prost(message, required, tag="2")] + pub last_log_idx: LogIdx, + #[prost(message, required, tag="3")] + pub last_log_term: TermId, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VoteResponse { + #[prost(bool, required, tag="2")] + pub vote_granted: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AppendRequest { + #[prost(message, required, tag="1")] + pub prev_log_idx: LogIdx, + #[prost(message, required, tag="2")] + pub prev_log_term: TermId, + #[prost(message, required, tag="3")] + pub leader_commit: LogIdx, + #[prost(message, repeated, tag="4")] + pub entries: ::std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AppendResponse { + #[prost(bool, required, tag="1")] + pub success: bool, + #[prost(message, required, tag="2")] + pub match_idx: LogIdx, + #[prost(message, required, tag="3")] + pub last_log_idx: LogIdx, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LogEntry { + #[prost(message, required, tag="1")] + pub term: TermId, + #[prost(bytes, required, tag="2")] + pub data: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RaftGroupId { + #[prost(bytes, required, tag="1")] + pub id: std::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TermId { + #[prost(uint64, required, tag="1")] + pub id: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LogIdx { + #[prost(uint64, required, tag="1")] + pub id: u64, +} diff --git a/enclave/kbupd_enclave/src/protobufs_impl/kbupd.rs b/enclave/kbupd_enclave/src/protobufs_impl/kbupd.rs new file mode 100644 index 0000000..00c27a2 --- /dev/null +++ b/enclave/kbupd_enclave/src/protobufs_impl/kbupd.rs @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::cmp::*; +use std::fmt; +use std::ops::*; +use crate::prelude::*; + + +use crate::util::*; +use crate::protobufs::kbupd::*; +use crate::protobufs::kbupd_enclave; +use crate::protobufs::kbupd_client; + +// +// ServiceId impls +// + +impl fmt::Display for ServiceId { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { id } = self; + write!(fmt, "{}", ToHex(id)) + } +} + +// +// BackupId impls +// + +impl BackupId { + pub const LENGTH: usize = 32; + pub fn valid_len() -> u32 { + 32 + } + + pub fn try_from_slice(slice: T) -> Result + where T: AsRef<[u8]>, + { + let slice: &[u8] = slice.as_ref(); + if slice.len() == Self::LENGTH { + let id = slice.to_vec(); + Ok(Self { id }) + } else { + Err(()) + } + } + pub fn try_to_array(&self) -> Result<[u8; Self::LENGTH], ()> { + let mut array = [0; Self::LENGTH]; + if self.id.len() == array.len() { + array.copy_from_slice(&self.id); + Ok(array) + } else { + Err(()) + } + } +} + +impl From<[u8; 32]> for BackupId { + fn from(from: [u8; 32]) -> Self { + Self { id: from.to_vec() } + } +} + +impl AsRef<[u8]> for BackupId { + fn as_ref(&self) -> &[u8] { + &self.id + } +} + +impl Deref for BackupId { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.id + } +} + +impl Eq for BackupId {} +impl PartialOrd for BackupId { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } +} +impl Ord for BackupId { + fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } +} + +impl fmt::Display for BackupId { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { id } = self; + write!(fmt, "{}", ToHex(id)) + } +} + +// +// PartitionKeyRangePB +// + +impl fmt::Display for PartitionKeyRangePb { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { first, last } = self; + write!(fmt, "{}-{}", ToHex(first), ToHex(last)) + } +} + +// +// UntrustedTransactionRequest +// + +impl fmt::Display for UntrustedTransactionRequest { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, fmt) + } +} + +// +// EnclaveFrontendRequestTransaction +// + +impl enclave_frontend_request_transaction::Transaction { + pub fn from_reply(backup_id: BackupId, reply_data: &kbupd_enclave::transaction_reply::Data) -> Self { + use enclave_frontend_request_transaction::{Transaction}; + use kbupd_enclave::transaction_reply::{Data as ReplyData}; + use kbupd_enclave::*; + + match reply_data { + ReplyData::CreateBackupReply(CreateBackupReply { .. }) => + Transaction::Create(EnclaveCreateBackupTransaction { backup_id }), + + ReplyData::ClientResponse(kbupd_client::Response { backup: Some(backup_response), .. }) => + Transaction::Backup(EnclaveBackupTransaction { + backup_id, + status: backup_response.status.unwrap_or_default(), + }), + + ReplyData::ClientResponse(kbupd_client::Response { restore: Some(restore_response), .. }) => + Transaction::Restore(EnclaveRestoreTransaction { + backup_id, + status: restore_response.status.unwrap_or_default(), + }), + + ReplyData::ClientResponse(kbupd_client::Response { delete: Some(_), .. }) | + ReplyData::DeleteBackupReply(DeleteBackupReply { .. }) => + Transaction::Delete(EnclaveDeleteBackupTransaction { backup_id }), + + ReplyData::WrongPartition(TransactionErrorWrongPartition { new_partition, .. }) => + Transaction::WrongPartition(EnclaveTransactionErrorWrongPartition { + new_partition_unknown: new_partition.is_none(), + }), + + ReplyData::XferInProgress(TransactionErrorXferInProgress {}) => + Transaction::XferInProgress(EnclaveTransactionErrorXferInProgress {}), + + ReplyData::ClientResponse(kbupd_client::Response { backup: None, restore: None, delete: None }) | + ReplyData::InvalidRequest(TransactionErrorInvalidRequest {}) => + Transaction::InvalidRequest(EnclaveTransactionErrorInvalidRequest {}), + + ReplyData::NotLeader(TransactionErrorNotLeader { .. }) | + ReplyData::ServiceIdMismatch(TransactionErrorServiceIdMismatch {}) | + ReplyData::InternalError(TransactionErrorInternalError {}) => + Transaction::InternalError(EnclaveTransactionErrorInternalError {}), + } + } +} + + +// +// EnclaveFrontendConfig +// + +impl fmt::Display for EnclaveFrontendConfig { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, fmt) + } +} + +// +// EnclaveReplicaConfig +// + +impl fmt::Display for EnclaveReplicaConfig { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, fmt) + } +} + +// +// SourcePartitionConfig +// + +impl fmt::Display for SourcePartitionConfig { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { range, node_ids } = self; + fmt.debug_struct("SourcePartitionConfig") + .field("range", &DisplayAsDebug(range)) + .field("node_ids", &ListDisplay(node_ids.iter().map(|node_id| ToHex(node_id)))) + .finish() + } +} diff --git a/enclave/kbupd_enclave/src/protobufs_impl/kbupd_client.rs b/enclave/kbupd_enclave/src/protobufs_impl/kbupd_client.rs new file mode 100644 index 0000000..e2e67b3 --- /dev/null +++ b/enclave/kbupd_enclave/src/protobufs_impl/kbupd_client.rs @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use sgx_ffi::util::{clear}; + +use crate::protobufs::kbupd_client::*; + +// +// BackupRequest impls +// + +impl Drop for BackupRequest { + fn drop(&mut self) { + if let Some(data) = &mut self.data { + clear(data); + } + if let Some(pin) = &mut self.pin { + clear(pin); + } + } +} + +// +// RestoreRequest impls +// + +impl Drop for RestoreRequest { + fn drop(&mut self) { + if let Some(pin) = &mut self.pin { + clear(pin); + } + } +} + +// +// RestoreResponse impls +// + +impl Drop for RestoreResponse { + fn drop(&mut self) { + if let Some(data) = &mut self.data { + clear(data); + } + } +} diff --git a/enclave/kbupd_enclave/src/protobufs_impl/kbupd_enclave.rs b/enclave/kbupd_enclave/src/protobufs_impl/kbupd_enclave.rs new file mode 100644 index 0000000..0858061 --- /dev/null +++ b/enclave/kbupd_enclave/src/protobufs_impl/kbupd_enclave.rs @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fmt; + +use sgx_ffi::util::{clear}; + +use crate::protobufs::kbupd::*; +use crate::protobufs::kbupd_enclave::*; +use crate::util::*; + +// +// SecretBytes impls +// + +impl Drop for SecretBytes { + fn drop(&mut self) { + clear(&mut self.data); + } +} + +// +// FrontendRequestTransaction impls +// + +impl FrontendRequestTransaction { + pub fn backup_id(&self) -> Option<&BackupId> { + match &self.transaction { + Some(frontend_request_transaction::Transaction::Backup(backup)) => Some(&backup.backup_id), + Some(frontend_request_transaction::Transaction::Restore(restore)) => Some(&restore.backup_id), + Some(frontend_request_transaction::Transaction::Create(create)) => Some(&create.backup_id), + Some(frontend_request_transaction::Transaction::Delete(delete)) => Some(&delete.backup_id), + None => None + } + } +} + +// +// PeerConnectRequest impls +// + +impl fmt::Display for PeerConnectRequest { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Debug::fmt(self, fmt) + } +} + +// +// XferRequest impls +// + +impl fmt::Display for XferRequest { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let Self { chunk_size, full_range, node_ids, group_id } = self; + fmt.debug_struct("XferRequest") + .field("chunk_size", chunk_size) + .field("full_range", &DisplayAsDebug(full_range)) + .field("node_ids", &ListDisplay(node_ids.iter().map(|node_id| ToHex(node_id)))) + .field("group_id", &DisplayAsDebug(group_id)) + .finish() + } +} diff --git a/enclave/kbupd_enclave/src/protobufs_impl/mod.rs b/enclave/kbupd_enclave/src/protobufs_impl/mod.rs new file mode 100644 index 0000000..b716968 --- /dev/null +++ b/enclave/kbupd_enclave/src/protobufs_impl/mod.rs @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +mod kbupd; +mod kbupd_client; +mod kbupd_enclave; +mod raft; diff --git a/enclave/kbupd_enclave/src/protobufs_impl/raft.rs b/enclave/kbupd_enclave/src/protobufs_impl/raft.rs new file mode 100644 index 0000000..0922e07 --- /dev/null +++ b/enclave/kbupd_enclave/src/protobufs_impl/raft.rs @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::prelude::*; + +use std::mem; + +use sgx_ffi::util::{clear, SecretValue}; + +use crate::protobufs::raft::*; + +// +// LogEntry impls +// + +impl LogEntry { + pub fn new(term: TermId, data: SecretValue>) -> Self { + Self { + term, + data: data.into_inner(), + } + } + + pub fn into_data(mut self) -> SecretValue> { + SecretValue::new(mem::replace(&mut self.data, Vec::new())) + } +} + +impl Drop for LogEntry { + fn drop(&mut self) { + clear(&mut self.data); + } +} diff --git a/enclave/kbupd_enclave/src/raft.proto b/enclave/kbupd_enclave/src/raft.proto new file mode 100644 index 0000000..4ff95cf --- /dev/null +++ b/enclave/kbupd_enclave/src/raft.proto @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +syntax = "proto2"; + +package protobufs.raft; + +message RaftMessage { + required RaftGroupId group = 1; + required TermId term = 2; + oneof inner { + VoteRequest vote_request = 3; + VoteResponse vote_response = 4; + AppendRequest append_request = 5; + AppendResponse append_response = 6; + }; +} + +message VoteRequest { + required LogIdx last_log_idx = 2; + required TermId last_log_term = 3; +} + +message VoteResponse { + required bool vote_granted = 2; +} + +message AppendRequest { + required LogIdx prev_log_idx = 1; + required TermId prev_log_term = 2; + required LogIdx leader_commit = 3; + repeated LogEntry entries = 4; +} + +message AppendResponse { + required bool success = 1; + required LogIdx match_idx = 2; + required LogIdx last_log_idx = 3; +} + +message LogEntry { + required TermId term = 1; + required bytes data = 2; +} + +message RaftGroupId { + required bytes id = 1; +} + +message TermId { + required uint64 id = 1; +} + +message LogIdx { + required uint64 id = 1; +} diff --git a/enclave/kbupd_enclave/src/raft.rs b/enclave/kbupd_enclave/src/raft.rs new file mode 100644 index 0000000..06c1d92 --- /dev/null +++ b/enclave/kbupd_enclave/src/raft.rs @@ -0,0 +1,1109 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::prelude::*; + +use std::cmp::{Ordering}; +use std::collections::{BTreeMap, BTreeSet}; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::iter; +use std::ops::{Add, AddAssign, Sub}; + +use prost::{Message}; +use rand_core::{RngCore}; + +use crate::protobufs::raft::*; +use self::LeadershipState::*; + +pub struct ReplicationState { + // \* The next entry to send to each follower. + // VARIABLE nextIndex + pub next_idx: LogIdx, + + // \* The latest entry that each follower has acknowledged is the same as the + // \* leader's. This is used to calculate commitIndex on the leader. + // VARIABLE matchIndex + pub match_idx: LogIdx, + + pub inflight: Option, + pub send_probe: bool, + send_heartbeat: bool, +} + +// \* Server states. +// CONSTANTS Follower, Candidate, Leader +enum LeadershipState { + Follower(FollowerState), + Candidate(CandidateState), + Leader(LeaderState), +} + +struct FollowerState { + leader: Option, + + election_ticks: u32, + random_election_ticks: u32, +} + +struct CandidateState { + // \* The latest entry that each follower has acknowledged is the same as the + // \* leader's. This is used to calculate commitIndex on the leader. + // VARIABLE votesGranted + votes_granted: BTreeSet, + + election_ticks: u32, +} + +struct LeaderState { + followers: BTreeMap, + + heartbeat_ticks: u32, +} + +pub struct RaftState { + group: RaftGroupId, + node_id: NodeId, + peers: BTreeSet, + random: Random, + + election_timeout_ticks: u32, + heartbeat_timeout_ticks: u32, + replication_chunk_size: usize, + + last_applied: LogIdx, + + // \* The server's term number. + // VARIABLE currentTerm + current_term: TermId, + + // \* The candidate the server voted for in its current term, or + // \* Nil if it hasn't voted for any. + // VARIABLE votedFor + voted_for: Option, + + // \* The server's state (Follower, Candidate, or Leader). + // VARIABLE state + leadership: LeadershipState, + + // \* A Sequence of log entries. The index into this sequence is the index of the + // \* log entry. Unfortunately, the Sequence module defines Head(s) as the entry + // \* with index 1, so be careful not to use that! + // VARIABLE log + log: Log, + + // \* The index of the latest entry in the log the state machine may apply. + // VARIABLE commitIndex + commit_idx: LogIdx, +} + +pub enum SendableRaftMessage { + Broadcast { + message: RaftMessage, + }, + Reply { + message: RaftMessage, + from: NodeId, + }, +} + +pub trait RaftLog { + fn append(&mut self, log_entry: LogEntry) -> Result<(), RaftLogAppendError>; + fn pop_front(&mut self, truncate_to: LogIdx) -> Result<(), ()>; + fn cancel_from(&mut self, from_log_idx: LogIdx) -> Result; + fn get(&mut self, log_idx: LogIdx) -> Option; + fn get_term(&mut self, log_idx: LogIdx) -> Option; + fn get_len(&mut self, log_idx: LogIdx) -> Option; + fn prev_idx(&self) -> LogIdx; + fn last_idx(&self) -> LogIdx; + fn last_term(&self) -> TermId; +} + +#[allow(variant_size_differences)] +pub enum RaftLogAppendError { + TooLarge { + size: usize, + }, + OutOfSpace { + log_entry: LogEntry, + }, + InternalError, +} + +impl RaftState +where Log: RaftLog, + Random: RngCore, + NodeId: Ord + Clone + fmt::Display, +{ + #[allow(clippy::too_many_arguments)] + pub fn new(group: RaftGroupId, + node_id: NodeId, + mut peers: BTreeSet, + log: Log, + mut random: Random, + election_timeout_ticks: u32, + heartbeat_timeout_ticks: u32, + replication_chunk_size: usize) + -> Self { + peers.remove(&node_id); + let random_election_ticks = random_election_timeout(&mut random, election_timeout_ticks); + Self { + group, + node_id, + peers, + random, + election_timeout_ticks, + heartbeat_timeout_ticks, + replication_chunk_size, + last_applied: Default::default(), + current_term: Default::default(), + voted_for: Default::default(), + leadership: Follower(FollowerState { + leader: None, + election_ticks: random_election_ticks, + random_election_ticks, + }), + log, + commit_idx: Default::default(), + } + } + + pub fn group_id(&self) -> &RaftGroupId { + &self.group + } + + pub fn take_committed_transaction(&mut self) -> Option { + if self.last_applied < self.commit_idx { + let log_idx = self.last_applied + 1; + let log_entry = self.log.get(log_idx)?; + self.last_applied = log_idx; + Some(log_entry) + } else { + None + } + } + + pub fn last_applied(&self) -> &LogIdx { + &self.last_applied + } + + pub fn commit_idx(&self) -> &LogIdx { + &self.commit_idx + } + + pub fn log(&self) -> &Log { + &self.log + } + + pub fn log_mut(&mut self) -> &mut Log { + &mut self.log + } + + pub fn peers(&self) -> &BTreeSet { + &self.peers + } + + pub fn replication_state(&self, peer_node_id: &NodeId) -> Option<&ReplicationState> { + if let LeadershipState::Leader(leader_state) = &self.leadership { + leader_state.followers.get(peer_node_id) + } else { + None + } + } + + pub fn leader(&self) -> (Option<&NodeId>, &TermId) { + let leader = match &self.leadership { + Follower(follower_state) => follower_state.leader.as_ref(), + Candidate(_) => None, + Leader(_) => Some(&self.node_id), + }; + (leader, &self.current_term) + } + + pub fn is_leader(&self) -> bool { + if let Leader(_) = &self.leadership { + true + } else { + false + } + } + + pub fn set_election_timeout_ticks(&mut self, election_timeout_ticks: u32) { + self.election_timeout_ticks = election_timeout_ticks; + + match &mut self.leadership { + Follower(FollowerState { election_ticks, random_election_ticks, .. }) => { + if *random_election_ticks > election_timeout_ticks.saturating_mul(2) { + *random_election_ticks = random_election_timeout(&mut self.random, election_timeout_ticks); + } + if election_ticks > random_election_ticks { + *election_ticks = *random_election_ticks; + } + } + Candidate(CandidateState { election_ticks, .. }) => { + if *election_ticks > election_timeout_ticks.saturating_mul(2) { + *election_ticks = random_election_timeout(&mut self.random, election_timeout_ticks); + } + } + Leader(_) => (), + } + } + + pub fn set_heartbeat_timeout_ticks(&mut self, heartbeat_timeout_ticks: u32) { + self.heartbeat_timeout_ticks = heartbeat_timeout_ticks; + + match &mut self.leadership { + Leader(LeaderState { heartbeat_ticks, .. }) => { + if *heartbeat_ticks > heartbeat_timeout_ticks { + *heartbeat_ticks = heartbeat_timeout_ticks; + } + } + Follower(_) | Candidate(_) => (), + } + } + + pub fn set_replication_chunk_size(&mut self, replication_chunk_size: usize) { + self.replication_chunk_size = replication_chunk_size; + } + + pub fn timer_tick(&mut self) -> Option> { + match &mut self.leadership { + Follower(FollowerState { election_ticks, .. }) | + Candidate(CandidateState { election_ticks, .. }) => { + match election_ticks.saturating_sub(1) { + 0 => { + info!("election timeout at {}", &self.current_term); + self.timeout() + } + new_election_ticks => { + *election_ticks = new_election_ticks; + None + } + } + } + Leader(leader_state) => { + match leader_state.heartbeat_ticks.saturating_sub(1) { + 0 => { + leader_state.heartbeat_ticks = self.heartbeat_timeout_ticks; + debug!("sending heartbeat"); + for replication in leader_state.followers.values_mut() { + replication.send_heartbeat = true; + } + } + new_heartbeat_ticks => { + leader_state.heartbeat_ticks = new_heartbeat_ticks; + } + } + None + } + } + } + + pub fn reset_peer(&mut self, peer_node_id: NodeId) -> Option> { + match &mut self.leadership { + Follower(_) => { + None + } + Candidate(_) => { + let vote_request = self.request_vote(); + let from = peer_node_id; + vote_request.map(|message| SendableRaftMessage::Reply { message, from }) + } + Leader(leader_state) => { + if let Some(replication) = leader_state.followers.get_mut(&peer_node_id) { + info!("resetting follower state {}", &peer_node_id); + replication.next_idx = self.log.last_idx() + 1; + replication.send_probe = true; + replication.send_heartbeat = true; + replication.inflight = None; + } + None + } + } + } + + fn log_append(&mut self, mut entry: LogEntry) -> Result<(), ()> { + while let Err(append_error) = self.log.append(entry) { + match append_error { + RaftLogAppendError::OutOfSpace { log_entry } => { + match self.log.pop_front(self.last_applied) { + Ok(()) => { + entry = log_entry; + } + Err(()) => { + error!("truncated entire raft log and still didn't have {} bytes for log index {}!", + log_entry.data.len(), self.log.last_idx() + 1); + return Err(()); + } + } + } + RaftLogAppendError::TooLarge { size } => { + error!("transaction of {} bytes at raft log index {} is too large!", + size, self.log.last_idx() + 1); + return Err(()); + } + RaftLogAppendError::InternalError => { + error!("error writing raft log index {}", self.log.last_idx() + 1); + return Err(()); + } + } + } + Ok(()) + } + + // + // -- raft TLA+ parallel code -- + // the code below is so similar to Raft's TLA+ code that the TLA+ is provided + // in the right-hand column for sections which correspond almost exactly. code + // is provided in the same order as the TLA+ so that the reader can follow. + // + + // + // \* Define state transitions + // + + // \* Server i times out and starts a new election. + pub fn timeout(&mut self) -> Option> { // Timeout(i) == + match &self.leadership { + Follower(_) | Candidate(_) => { // /\ state[i] \in {Follower, Candidate} + self.current_term += 1; // /\ currentTerm' = [currentTerm EXCEPT ![i] = currentTerm[i] + 1] + // \* Most implementations would probably just set the local vote + // \* atomically, but messaging localhost for it is weaker. + self.voted_for = Some(self.node_id.clone()); // /\ votedFor' = [votedFor EXCEPT ![i] = Nil] + let votes_granted = iter::once(self.node_id.clone()).collect(); // /\ votesGranted' = [votesGranted EXCEPT ![i] = {}] + self.leadership = Candidate(CandidateState { // /\ state' = [state EXCEPT ![i] = Candidate] + votes_granted, + election_ticks: self.random_election_timeout(), + }); + + info!("became candidate at {}", self.current_term); + self.become_leader(); + self.advance_commit_idx(); + self.request_vote().map(|message| SendableRaftMessage::Broadcast { message }) + } + Leader(_) => { + None + } + } + } + + // \* Candidate i sends j a RequestVote request. + fn request_vote(&self) -> Option { // RequestVote(i,j) == + match self.leadership { + Candidate { .. } => { // /\ state[i] = Candidate + let vote_request_msg = RaftMessage { // /\ Send([ + group: self.group.clone(), + term: self.current_term, // mterm |-> currentTerm[i], + inner: Some(raft_message::Inner::VoteRequest(VoteRequest { // mtype |-> RequestVoteRequest, + last_log_term: self.log.last_term(), // mlastLogTerm |-> LastTerm(log[i]), + last_log_idx: self.log.last_idx(), // mlastLogIndex |-> Len(log[i]), + })), + }; + Some(vote_request_msg) + } + _ => None, + } + } + + // \* Leader i sends j an AppendEntries request containing up to 1 entry. + // \* While implementations may want to send more than 1 at a time, this spec uses + // \* just 1 because it minimizes atomic regions without loss of generality. + pub fn append_entries(&mut self, + to_node_id: NodeId) + -> Option> { // AppendEntries(i, j) == + if let Leader(leader_state) = &mut self.leadership { // /\ state[i] = Leader + let replication = + match leader_state.followers.get_mut(&to_node_id) { // /\ i /= j + Some(replication) => replication, + None => return None, + }; + let last_log_idx = self.log.last_idx(); + let next_idx = replication.next_idx; + let send_entries = (last_log_idx >= next_idx && + !replication.send_probe); + if !send_entries && !replication.send_heartbeat { + return None; + } + if replication.inflight.is_some() { + return None; + } + let prev_log_idx = next_idx - 1; // /\ LET prevLogIndex == nextIndex[i][j] - 1 + let maybe_prev_log_term = if prev_log_idx != Default::default() { // prevLogTerm == IF prevLogIndex > 0 THEN + self.log.get_term(prev_log_idx) // log[i][prevLogIndex].term + } else { // ELSE + Some(Default::default()) // 0 + }; + + let prev_log_term = match maybe_prev_log_term { + Some(prev_log_term) => prev_log_term, + None => { + error!("missing log {} to send to {}!", + &prev_log_idx, &to_node_id); + return None; + } + }; + + let mut entries: Vec = Vec::new(); + let last_entry: LogIdx; + if send_entries { // \* Send up to 1 entry, constrained by the end of the log. + let mut entries_size = 0usize; + let max_entries_size = self.replication_chunk_size; + let entry_log_idxs = (0..).map(|idx| next_idx + idx) + .take_while(|log_idx| *log_idx <= last_log_idx); + for entry_log_idx in entry_log_idxs { // entries == SubSeq(log[i], nextIndex[i][j], lastEntry) + let append_log_entry = if let Some(log_entry) = self.log.get(entry_log_idx) { + let first_entry = entries_size == 0; + if !first_entry && entries_size == max_entries_size { + None + } else { + entries_size = entries_size.saturating_add(log_entry.encoded_len()); + if first_entry || entries_size <= max_entries_size { + Some(log_entry) + } else { + None + } + } + } else { + error!("error fetching raft log {} to send to {}!", + &entry_log_idx, &to_node_id); + None + }; + if let Some(log_entry) = append_log_entry { + entries.push(log_entry); + } else { + break; + } + } + last_entry = prev_log_idx + (entries.len() as u64); // lastEntry == Min({Len(log[i]), nextIndex[i][j]}) + } else { + last_entry = prev_log_idx; + } + let append_request_msg = RaftMessage { // IN Send([ + group: self.group.clone(), + term: self.current_term, // mterm |-> currentTerm[i], + inner: Some(raft_message::Inner::AppendRequest(AppendRequest { // mtype |-> AppendEntriesRequest, + prev_log_idx, // mprevLogIndex |-> prevLogIndex, + prev_log_term, // mprevLogTerm |-> prevLogTerm, + entries, // mentries |-> entries, + leader_commit: self.commit_idx.min(last_entry), // mcommitIndex |-> Min({commitIndex[i], lastEntry}), + })), + }; + replication.send_heartbeat = false; + replication.inflight = Some(last_entry); + Some(SendableRaftMessage::Reply { + message: append_request_msg, + from: to_node_id, + }) + } else { + None + } + } + + // \* Candidate i transitions to leader. + fn become_leader(&mut self) { // BecomeLeader(i) == + if let Candidate(candidate_state) = &self.leadership { // /\ state[i] = Candidate + if candidate_state.votes_granted.len() >= self.quorum_size() { // /\ votesGranted[i] \in Quorum + info!("became leader at {}", &self.current_term); + self.leadership = Leader(LeaderState { // /\ state' = [state EXCEPT ![i] = Leader] + followers: (self.peers.iter().cloned()) + .map(|id| (id, ReplicationState { + next_idx: self.log.last_idx() + 1, // /\ nextIndex' = [nextIndex EXCEPT ![i] = [j \in Server |-> Len(log[i]) + 1]] + match_idx: Default::default(), // /\ matchIndex' = [matchIndex EXCEPT ![i] = [j \in Server |-> 0]] + inflight: Default::default(), + send_probe: Default::default(), + send_heartbeat: Default::default(), + })).collect(), + heartbeat_ticks: 0, + }); + // append a noop in the new term to commit entries from past terms (Raft Section 5.4.2) + let _ignore = self.client_request(vec![]); + } + } + } + + // \* Leader i receives a client request to add v to the log. + pub fn client_request(&mut self, data: Vec) -> Result<(), ()> { // ClientRequest(i, v) == + let entry = LogEntry { + term: self.current_term, // /\ LET entry == [term |-> currentTerm[i], + data, // value |-> v] + }; + if let Leader(_) = &self.leadership { // /\ state[i] = Leader + self.log_append(entry)?; // newLog == Append(log[i], entry) + self.advance_commit_idx(); + Ok(()) // IN log' = [log EXCEPT ![i] = newLog] + } else { + Err(()) + } + } + + // \* Leader i advances its commitIndex. + // \* This is done as a separate step from handling AppendEntries responses, + // \* in part to minimize atomic regions, and in part so that leaders of + // \* single-server clusters are able to mark entries committed. + fn advance_commit_idx(&mut self) { // AdvanceCommitIndex(i) == + if let Leader(leader_state) = &self.leadership { // /\ state[i] = Leader + let mut match_idxs: Vec<_> = // /\ LET \* The set of servers that agree up through index. + (leader_state.followers.values()) + .map(|follower| follower.match_idx) + .chain(iter::once(self.log.last_idx())) + .collect(); + match_idxs.sort_unstable(); // Agree(index) == {i} \cup {k \in Server : matchIndex[i][k] >= index} + let agree_idxs = (match_idxs.into_iter()) // \* The maximum indexes for which a quorum agrees + .rev().skip(self.quorum_size() - 1); // agreeIndexes == {index \in 1..Len(log[i]) : Agree(index) \in Quorum} + let commit_idx = match agree_idxs.max() { // \* New value for commitIndex'[i] + Some(agree_idx) => { // newCommitIndex == IF /\ agreeIndexes /= {} + if self.log.get_term(agree_idx) == Some(self.current_term) {// /\ log[i][Max(agreeIndexes)].term = currentTerm[i] + self.commit_idx.max(agree_idx) // THEN Max(agreeIndexes) + } else { + self.commit_idx // ELSE commitIndex[i] + } + } + None => self.commit_idx, + }; + if commit_idx != self.commit_idx { + debug!("committed transactions from {} to {}", + &self.commit_idx, &commit_idx); + } + self.commit_idx = commit_idx; // IN commitIndex' = [commitIndex EXCEPT ![i] = newCommitIndex] + } + } + + // + // \* Message handlers + // \* i = recipient, j = sender, m = message + // + + // \* Server i receives a RequestVote request from server j with + // \* m.mterm <= currentTerm[i]. + fn handle_vote_request(&mut self, + msg_term: TermId, + msg: VoteRequest, + from: NodeId) + -> Option> { // HandleRequestVoteRequest(i, j, m) == + let last_log_idx = self.log.last_idx(); + let last_log_term = self.log.last_term(); + let log_ok = // LET logOk == + (msg.last_log_term > last_log_term) || // \/ m.mlastLogTerm > LastTerm(log[i]) + (msg.last_log_term == last_log_term && // \/ /\ m.mlastLogTerm = LastTerm(log[i]) + msg.last_log_idx >= last_log_idx); // /\ m.mlastLogIndex >= Len(log[i]) + let grant = // LET grant == + msg_term == self.current_term && // /\ m.mterm = currentTerm[i] + log_ok && // /\ logOk + self.voted_for.as_ref().map(|vote| &from == vote).unwrap_or(true); // /\ votedFor[i] \in {Nil, j} + assert!(msg_term <= self.current_term); // IN /\ m.mterm <= currentTerm[i] + if grant { + self.voted_for = Some(from.clone()); // /\ \/ grant /\ votedFor' = [votedFor EXCEPT ![i] = j] + } // \/ ~grant /\ UNCHANGED votedFor + + if grant { + info!("granted vote at {} with {} at {} for node {} with {} at {}", + &self.current_term, &last_log_idx, &last_log_term, + &from, &msg.last_log_idx, &msg.last_log_term); + match &mut self.leadership { + Follower(FollowerState { election_ticks, random_election_ticks, .. }) => + *election_ticks = *random_election_ticks, + Candidate(_) | Leader(_) => (), + } + } else if msg_term != self.current_term { + info!("ignored message with {} < current {}: {}", + &msg_term, &self.current_term, &msg); + } else if let Some(vote) = &self.voted_for { + info!("rejected vote at {} for node {} as already voted for {}", + &self.current_term, &from, vote); + } else { + info!("rejected vote at {} with {} at {} for node {} with {} at {}", + &self.current_term, &last_log_idx, &last_log_term, + &from, &msg.last_log_idx, &msg.last_log_term); + } + + let message = RaftMessage { // /\ Reply([ + group: self.group.clone(), + term: self.current_term, // mterm |-> currentTerm[i], + inner: Some(raft_message::Inner::VoteResponse(VoteResponse { // mtype |-> RequestVoteResponse, + vote_granted: grant, // mvoteGranted |-> grant, + })), + }; + Some(SendableRaftMessage::Reply { message, from }) + } + + // \* Server i receives a RequestVote response from server j with + // \* m.mterm = currentTerm[i]. + fn handle_vote_response(&mut self, + msg_term: TermId, + msg: VoteResponse, + from: NodeId) + -> Option> { // HandleRequestVoteResponse(i, j, m) == + assert!(msg_term == self.current_term); // /\ m.mterm = currentTerm[i] + if let Candidate(candidate_state) = &mut self.leadership { + if msg.vote_granted { // /\ \/ /\ m.mvoteGranted + info!("received vote granted from {} at {}", + &from, &self.current_term); + candidate_state.votes_granted.insert(from); // /\ votesGranted' = [votesGranted EXCEPT ![i] = votesGranted[i] \cup {j}] + } else { // \/ /\ ~m.mvoteGranted /\ UNCHANGED <> + info!("received vote rejected from {} at {}", + &from, &self.current_term); + } + } + None + } + + // \* Server i receives an AppendEntries request from server j with + // \* m.mterm <= currentTerm[i]. This just handles m.entries of length 0 or 1, but + // \* implementations could safely accept more by treating them the same as + // \* multiple independent requests of 1 entry. + fn handle_append_request(&mut self, + msg_term: TermId, + msg: AppendRequest, + from: NodeId) + -> Option> { // HandleAppendEntriesRequest(i, j, m) == + let prev_log_idx = msg.prev_log_idx; + let msg_prev_log_term = msg.prev_log_term; + let our_prev_log_term = self.log.get_term(prev_log_idx); + let log_ok = + prev_log_idx == Default::default() || // LET logOk == \/ m.mprevLogIndex = 0 + Some(msg_prev_log_term) == our_prev_log_term; // \/ /\ m.mprevLogIndex > 0 /\ m.mprevLogIndex <= Len(log[i]) /\ m.mprevLogTerm = log[i][m.mprevLogIndex].term + assert!(msg_term <= self.current_term); // IN /\ m.mterm <= currentTerm[i] + // /\ \/ \* return to follower state + if msg_term == self.current_term { // /\ m.mterm = currentTerm[i] + match &mut self.leadership { + Candidate(_) => { // /\ state[i] = Candidate + let random_election_ticks = self.random_election_timeout(); + self.leadership = Follower(FollowerState { // /\ state' = [state EXCEPT ![i] = Follower] + leader: Some(from.clone()), + election_ticks: random_election_ticks, + random_election_ticks, + }); + info!("became follower at {} of {}", &self.current_term, &from); + } + Follower(follower_state) => { + if follower_state.leader.is_none() { + info!("became follower at {} of {}", &self.current_term, &from); + } + follower_state.leader = Some(from.clone()); + follower_state.election_ticks = follower_state.random_election_ticks; + } + Leader { .. } => { + panic!("received append request as leader at {} from {}", + &self.current_term, &from); + } + } + } + // \/ /\ \* reject request + if (msg_term < self.current_term || // \/ m.mterm < currentTerm[i] + (assert_true!(msg_term == self.current_term) && // \/ /\ m.mterm = currentTerm[i] + assert_match!(Follower(_) = &self.leadership) && // /\ state[i] = Follower + !log_ok)) // /\ \lnot logOk + { + if msg_term < self.current_term { + info!("ignored message with {} < current {}: {}", + &msg_term, &self.current_term, &msg); + } else if let Some(our_prev_log_term) = our_prev_log_term { + warn!("rejected append from {} with {} at {}, we have {}", + &from, &prev_log_idx, msg_prev_log_term, &our_prev_log_term); + } else { + info!("rejected append from {} with {}, we are behind at {}", + &from, &prev_log_idx, self.log.last_idx()); + } + + let message = RaftMessage { // /\ Reply([ + group: self.group.clone(), + term: self.current_term, // mterm |-> currentTerm[i], + inner: Some(raft_message::Inner::AppendResponse(AppendResponse {// mtype |-> AppendEntriesResponse, + success: false, // msuccess |-> FALSE, + match_idx: self.log.prev_idx(), // mmatchIndex |-> 0, + last_log_idx: self.log.last_idx(), + })), + }; + Some(SendableRaftMessage::Reply { message, from }) + } else { // \/ \* accept request + assert!(msg_term == self.current_term); // /\ m.mterm = currentTerm[i] + assert_match!(Follower(_) = &self.leadership); // /\ state[i] = Follower + assert!(log_ok); // /\ logOk + // ... and the TLA+ that follows doesn't correspond to procedural code well + // find point of log conflict + let msg_last_log_idx = prev_log_idx + (msg.entries.len() as u64); + let msg_entries_iter = (1..).map(|idx| prev_log_idx + idx).zip(msg.entries); + let mut last_processed_idx = prev_log_idx; + for (msg_entry_log_idx, msg_entry) in msg_entries_iter { + if msg_entry_log_idx == self.log.last_idx() + 1 { + match self.log_append(msg_entry) { + Ok(()) => (), + Err(()) => break, + } + } else if let Some(our_entry_log_term) = self.log.get_term(msg_entry_log_idx) { + if our_entry_log_term != msg_entry.term { + assert!(msg_entry_log_idx > self.commit_idx); + match self.log.cancel_from(msg_entry_log_idx) { + Ok(cancelled_len) => + info!("cancelled {} transactions from {}", cancelled_len, &msg_entry_log_idx), + Err(()) => + break, + } + match self.log_append(msg_entry) { + Ok(()) => (), + Err(()) => break, + } + } + } else { + error!("failed to fetch log index {} to find conflicts for append!", &msg_entry_log_idx); + break; + } + last_processed_idx = msg_entry_log_idx; + } + + // update commit index from leader + let leader_commit = msg.leader_commit.min(last_processed_idx); + if leader_commit > self.commit_idx { + debug!("committed transactions from {} to {}", &self.commit_idx, &leader_commit); + + self.commit_idx = leader_commit; // /\ commitIndex' = [commitIndex EXCEPT ![i] = m.mcommitIndex] + } + + let message = RaftMessage { // /\ Reply([ + group: self.group.clone(), + term: self.current_term, // mterm |-> currentTerm[i], + inner: Some(raft_message::Inner::AppendResponse(AppendResponse {// mtype |-> AppendEntriesResponse, + success: true, // msuccess |-> TRUE, + match_idx: msg_last_log_idx.min(self.log.last_idx()), // mmatchIndex |-> m.mprevLogIndex + Len(m.mentries), + last_log_idx: self.log.last_idx(), + })), + }; + Some(SendableRaftMessage::Reply { message, from }) + } + } + + // \* Server i receives an AppendEntries response from server j with + // \* m.mterm = currentTerm[i]. + fn handle_append_response(&mut self, + msg_term: TermId, + msg: AppendResponse, + from: NodeId) + -> Option> { // HandleAppendEntriesResponse(i, j, m) == + assert!(msg_term == self.current_term); // /\ m.mterm = currentTerm[i] + if let Leader(leader_state) = &mut self.leadership { + if let Some(replication) = leader_state.followers.get_mut(&from) { + if msg.success { // /\ \/ /\ m.msuccess \* successful + if Some(msg.match_idx) >= replication.inflight { + replication.inflight = None; + } + if msg.match_idx + 1 > replication.next_idx { + replication.next_idx = msg.match_idx + 1; // /\ nextIndex' = [nextIndex EXCEPT ![i][j] = m.mmatchIndex + 1] + } + if msg.match_idx > replication.match_idx { + replication.match_idx = msg.match_idx; // /\ matchIndex' = [matchIndex EXCEPT ![i][j] = m.mmatchIndex] + } + replication.send_probe = false; + } else { // \/ /\ \lnot m.msuccess \* not successful + if !replication.send_probe { + info!("received append rejection at {} from {} having {}", + &replication.next_idx, &from, &msg.last_log_idx); + } else { + verbose!("received append rejection at {} from {} having {}", + &replication.next_idx, &from, &msg.last_log_idx); + } + replication.next_idx = ((replication.next_idx - 1) // /\ nextIndex' = [nextIndex EXCEPT ![i][j] = Max({nextIndex[i][j] - 1, 1})] + .min(msg.last_log_idx + 1) + .max(msg.match_idx + 1)); + replication.send_probe = true; + replication.inflight = None; + + let mut chunk_size_remaining = self.replication_chunk_size; + while let Some(next_idx) = replication.next_idx.checked_sub(1) { + if next_idx <= msg.match_idx { + break; + } + let entry_len = match self.log.get_len(replication.next_idx) { + Some(entry_len) => entry_len, + None => break, + }; + chunk_size_remaining = match chunk_size_remaining.checked_sub(entry_len) { + Some(new_chunk_size_remaining) => new_chunk_size_remaining, + None => break, + }; + replication.next_idx = next_idx; + } + } + } + } + None + } + + // \* Any RPC with a newer term causes the recipient to advance its term first. + fn update_term(&mut self, + from: &NodeId, + msg: &RaftMessage) { // UpdateTerm(i, j, m) == + if msg.term > self.current_term { // /\ m.mterm > currentTerm[i] + info!("became follower at {} (from {}) due to message from {}: {}", + &msg.term, &self.current_term, from, &msg); + let random_election_ticks = self.random_election_timeout(); + + let election_ticks = match &self.leadership { + Follower(FollowerState { election_ticks, .. }) | + Candidate(CandidateState { election_ticks, .. }) => + *election_ticks, + Leader(_) => + random_election_ticks, + }; + self.current_term = msg.term; // /\ currentTerm' = [currentTerm EXCEPT ![i] = m.mterm] + self.leadership = Follower(FollowerState { // /\ state' = [state EXCEPT ![i] = Follower] + leader: None, + election_ticks, + random_election_ticks, + }); + self.voted_for = Default::default(); // /\ votedFor' = [votedFor EXCEPT ![i] = Nil] + } + } + + // \* Responses with stale terms are ignored. + fn drop_stale_response(&self, + msg_term: TermId, + msg: T) + -> Result<(), T> + where T: fmt::Display + { // DropStaleResponse(i, j, m) == + if msg_term < self.current_term { // /\ m.mterm < currentTerm[i] + info!("ignored message with {} < current {}: {}", + &msg_term, &self.current_term, &msg); + drop(msg); // /\ Discard(m) + Ok(()) + } else { + Err(msg) + } + } + + // /* Receive a message. + pub fn receive(&mut self, + msg: RaftMessage, + from: NodeId) + -> Option> { // Receive(m) == + if msg.group != self.group { + error!("received raft message from {} for wrong group {}", + &from, &msg.group); + return None; + } + // IN \* Any RPC with a newer term causes the recipient to advance + // \* its term first. Responses with stale terms are ignored. + self.update_term(&from, &msg); // \/ UpdateTerm(i, j, m) + let reply = match msg.inner { + Some(raft_message::Inner::VoteRequest(request)) => // \/ /\ m.mtype = RequestVoteRequest + self.handle_vote_request(msg.term, request, from), // /\ HandleRequestVoteRequest(i, j, m) + Some(raft_message::Inner::VoteResponse(response)) => { // \/ /\ m.mtype = RequestVoteResponse + match self.drop_stale_response(msg.term, response) { // /\ \/ DropStaleResponse(i, j, m) + Ok(()) => None, + Err(response) => + self.handle_vote_response(msg.term, response, from), // \/ HandleRequestVoteResponse(i, j, m) + } + } + Some(raft_message::Inner::AppendRequest(request)) => // \/ /\ m.mtype = AppendEntriesRequest + self.handle_append_request(msg.term, request, from), // /\ HandleAppendEntriesRequest(i, j, m) + Some(raft_message::Inner::AppendResponse(response)) => { // \/ /\ m.mtype = AppendEntriesResponse + match self.drop_stale_response(msg.term, response) { // /\ \/ DropStaleResponse(i, j, m) + Ok(()) => None, + Err(response) => + self.handle_append_response(msg.term, response, from), // \/ HandleAppendEntriesResponse(i, j, m) + } + } + None => None, + }; + self.become_leader(); + self.advance_commit_idx(); + reply + } + + // + // helpers + // + + fn quorum_size(&self) -> usize { + quorum_size(self.peers.len()) + } + + fn random_election_timeout(&mut self) -> u32 { + random_election_timeout(&mut self.random, self.election_timeout_ticks) + } +} + +pub fn quorum_size(peer_count: usize) -> usize { + (peer_count.saturating_add(1)) / 2 + 1 +} + +fn random_election_timeout(random: &mut impl RngCore, election_timeout_ticks: u32) -> u32 { + let random = random.next_u32().checked_rem(election_timeout_ticks).unwrap_or(0); + election_timeout_ticks.saturating_add(random) +} + +// +// RaftMessage impls +// + +impl fmt::Display for RaftMessage { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { group: _, term, inner } = self; + let mut debug = fmt.debug_tuple(""); + debug.field(&format_args!("{}", term)); + if let Some(inner) = inner { + debug.field(&format_args!("{}", inner)); + } else { + debug.field(&"None"); + } + debug.finish() + } +} + +impl fmt::Display for raft_message::Inner { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + raft_message::Inner::VoteRequest(msg) => fmt::Display::fmt(msg, fmt), + raft_message::Inner::VoteResponse(msg) => fmt::Display::fmt(msg, fmt), + raft_message::Inner::AppendRequest(msg) => fmt::Display::fmt(msg, fmt), + raft_message::Inner::AppendResponse(msg) => fmt::Display::fmt(msg, fmt), + } + } +} + +impl fmt::Display for VoteRequest { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { last_log_idx, last_log_term } = self; + fmt.debug_struct("VoteRequest") + .field("last_log_idx", &format_args!("{}", last_log_idx)) + .field("last_log_term", &format_args!("{}", last_log_term)) + .finish() + } +} + +impl fmt::Display for VoteResponse { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { vote_granted } = self; + fmt.debug_struct("VoteResponse") + .field("vote_granted", vote_granted) + .finish() + } +} + +impl fmt::Display for AppendRequest { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { prev_log_idx, prev_log_term, leader_commit, entries } = self; + fmt.debug_struct("AppendRequest") + .field("prev_log_idx", &format_args!("{}", prev_log_idx)) + .field("prev_log_term", &format_args!("{}", prev_log_term)) + .field("leader_commit", &format_args!("{}", leader_commit)) + .field("entries", &entries.len()) + .finish() + } +} + +impl fmt::Display for AppendResponse { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { success, match_idx, last_log_idx } = self; + fmt.debug_struct("AppendResponse") + .field("success", &success) + .field("match_idx", &format_args!("{}", match_idx)) + .field("last_log_idx", &format_args!("{}", last_log_idx)) + .finish() + } +} + + +// +// TermId impls +// + +impl fmt::Display for TermId { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { id } = self; + fmt.debug_tuple("TermId") + .field(id) + .finish() + } +} + +impl Copy for TermId {} +impl Eq for TermId {} +impl PartialOrd for TermId { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } +} +impl Ord for TermId { + fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } +} +impl AddAssign for TermId { + fn add_assign(&mut self, rhs: u64) { + self.id = self.id.checked_add(rhs).unwrap_or_else(|| panic!("overflow")); + } +} + +// +// LogIdx impls +// + +impl LogIdx { + fn checked_sub(self, dec: u64) -> Option { + if let Some(id) = self.id.checked_sub(dec) { + Some(Self { id }) + } else { + None + } + } +} + +impl fmt::Display for LogIdx { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { id } = self; + fmt.debug_tuple("LogIdx") + .field(id) + .finish() + } +} + +impl Copy for LogIdx {} +impl Eq for LogIdx {} +impl PartialOrd for LogIdx { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } +} +impl Ord for LogIdx { + fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } +} +impl Add for LogIdx { + type Output = Self; + fn add(self, inc: u64) -> Self { + Self { id: self.id.checked_add(inc).unwrap_or_else(|| panic!("overflow")) } + } +} +impl Sub for LogIdx { + type Output = Self; + fn sub(self, dec: u64) -> Self { + Self { id: self.id.saturating_sub(dec) } + } +} + +impl Eq for RaftGroupId {} + +#[allow(clippy::derive_hash_xor_eq)] +impl Hash for RaftGroupId { + fn hash(&self, hasher: &mut H) { + self.id.hash(hasher) + } +} + +impl fmt::Display for RaftGroupId { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { id } = self; + for byte in id { + write!(fmt, "{:02x}", byte)?; + } + Ok(()) + } +} diff --git a/enclave/kbupd_enclave/src/remote/mod.rs b/enclave/kbupd_enclave/src/remote/mod.rs new file mode 100644 index 0000000..4d6bd03 --- /dev/null +++ b/enclave/kbupd_enclave/src/remote/mod.rs @@ -0,0 +1,1104 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +mod peer_manager; +mod sgx_quote; + +use crate::prelude::*; + +use std::borrow::*; +use std::cell::*; +use std::fmt; +use std::marker::*; +use std::ops::*; +use std::rc::*; +use std::time::*; + +use bytes::{BufMut}; +use chrono::{DateTime, NaiveDateTime, Utc}; +use num_traits::{ToPrimitive}; +use prost::{self, Message}; +use serde::{Deserialize}; +use sgx_ffi::sgx; +use sgx_ffi::util::{SecretValue}; +use sgxsd_ffi::{SHA256Context}; +use snow; + +use crate::{kbupd_send}; +use crate::ffi::snow_resolver::*; +use crate::protobufs::kbupd::*; +use crate::protobufs::kbupd::enclave_message::{Inner as EnclaveMessageInner}; +use crate::protobufs::kbupd_enclave::*; +use crate::util::{self, deserialize_base64}; + +use self::sgx_quote::*; + +// +// public api +// + +pub use self::peer_manager::*; + +pub const NODE_ID_LEN: usize = 32; + +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone)] +pub enum NodeId { + Valid([u8; NODE_ID_LEN]), + Invalid(Vec), +} + +#[derive(Clone)] +pub struct NodeParams { + node_key: Rc<[u8]>, + node_id: NodeId, + node_type: NodeType, +} + +pub struct RemoteSender +where M: prost::Message + 'static, +{ + id: NodeId, + shared: Rc>>, +} + +pub trait RemoteCommon { + fn id(&self) -> &NodeId; + fn attestation(&self) -> Option; +} + +pub trait RemoteMessageSender: RemoteCommon + fmt::Display { + type Message: prost::Message; + fn send(&self, message: Rc) -> Result<(), ()>; +} + +#[must_use] +pub enum RemoteRecvError { + NeedsAttestation(GetAttestationRequest), + DecodeError, + InvalidState, +} + +pub enum RemoteAuthorizationType { + Mutual, + RemoteOnly, + SelfOnly, +} + +pub trait Remote: RemoteCommon { + fn connect(&mut self) -> Result<(), ()>; + fn accept(&mut self, connect_request: PeerConnectRequest) -> Result<(), ()>; + fn qe_info_reply(&self, sgx_qe_info: &GetQeInfoReply) -> Result; + fn get_quote_reply(&mut self, sgx_quote: GetQuoteReply) -> Result, Option>; + fn attestation_reply(&mut self, ias_report: IasReport) -> Result, ()>; +} + +pub struct RemoteState +where M: prost::Message + 'static, + R: prost::Message + Default + 'static +{ + node_params: Rc, + remote_node_id: NodeId, + remote_type: NodeType, + auth_type: RemoteAuthorizationType, + shared: Rc>>, + _reply: PhantomData, +} + +#[derive(Clone, Default)] +pub struct SharedNoiseBuffers { + inner: Rc, +} + +// +// RemoteState impls +// + +const NOISE_PARAMS: &str = "Noise_KK_25519_AESGCM_SHA256"; + +const NOISE_CHUNK_MAX_LENGTH: usize = 65535; + +struct HandshakeHash { + hash: [u8; 32], +} + +struct Shared { + session: SessionState, + remote_node_id: NodeId, + noise_buffer: SharedNoiseBuffers, + _message: PhantomData, +} + +#[derive(Default)] +struct NoiseBuffers { + read_buffer: Cell>>>, + write_buffer: RefCell, +} + +struct NoiseBuffer([u8; NOISE_CHUNK_MAX_LENGTH]); + +// initiator: Disconnected -> WaitingForAttestation -> Initiated -> Connected -> Authorized +// responder: Disconnected -> Accepted -> Responded -> Authorized +#[allow(clippy::large_enum_variant)] +enum SessionState { + Disconnected, + WaitingForAttestation { + noise: snow::HandshakeState, + }, + Initiated { + noise: snow::HandshakeState, + }, + Connected { + noise: snow::TransportState, + their_handshake_hash: HandshakeHash, + final_handshake_hash: HandshakeHash, + }, + Accepted { + noise: snow::HandshakeState, + attestation: Option, + }, + Responded { + noise: snow::TransportState, + attestation: Option, + handshake_hash: HandshakeHash, + }, + Authorized { + noise: snow::TransportState, + attestation: Option, + handshake_hash: HandshakeHash, + }, +} + +impl RemoteState +where M: prost::Message + 'static, + R: prost::Message + Default + 'static +{ + pub fn new(node_params: Rc, + remote_node_id: NodeId, + remote_type: NodeType, + auth_type: RemoteAuthorizationType, + noise_buffer: SharedNoiseBuffers) + -> Self { + let shared = Rc::new(RefCell::new(Shared { + session: SessionState::Disconnected, + remote_node_id: remote_node_id.clone(), + noise_buffer, + _message: Default::default(), + })); + Self { + node_params, + remote_type, + remote_node_id, + auth_type, + shared, + _reply: Default::default(), + } + } + + pub fn sender(&self) -> RemoteSender { + RemoteSender { + id: self.remote_node_id.clone(), + shared: Rc::clone(&self.shared), + } + } + + fn create_noise_session(&self, initiator: bool, build_fun: BuildFun) -> Res + where BuildFun: Fn(snow::Builder<'_>) -> Res { + let mut prologue_buf = Vec::with_capacity(8); + if initiator { + prologue_buf.put_i32_le(self.node_params.node_type.into()); + prologue_buf.put_i32_le(self.remote_type.into()); + } else { + prologue_buf.put_i32_le(self.remote_type.into()); + prologue_buf.put_i32_le(self.node_params.node_type.into()); + } + let params = NOISE_PARAMS.parse().unwrap_or_else(|_| unreachable!()); + let builder = snow::Builder::with_resolver(params, Box::new(SnowResolver)) + .prologue(&prologue_buf) + .local_private_key(&self.node_params.node_key) + .remote_public_key(&self.remote_node_id); + build_fun(builder) + } + + fn initiate_connection(&self) -> Result { + self.create_noise_session(true, |builder| builder.build_initiator()) + } + + fn connection_request(noise: &mut snow::HandshakeState) -> Result, snow::Error> { + let mut msg_buf = vec![0; NOISE_CHUNK_MAX_LENGTH]; + let msg_len = noise.write_message(Default::default(), &mut msg_buf)?; + msg_buf.truncate(msg_len); + Ok(msg_buf) + } + + fn accept_connection(&self, msg_data: &[u8]) -> Result<(snow::HandshakeState, HandshakeHash), snow::Error> { + let mut noise = self.create_noise_session(false, |builder| builder.build_responder())?; + + let their_handshake_hash = get_handshake_hash(&noise)?; + + noise.read_message(msg_data, &mut [0; 0])?; + + Ok((noise, their_handshake_hash)) + } + + fn connection_response(mut noise: snow::HandshakeState) -> Result<(snow::TransportState, Vec, HandshakeHash), snow::Error> { + let mut msg_buf = vec![0; NOISE_CHUNK_MAX_LENGTH]; + let msg_len = noise.write_message(&[0;0], &mut msg_buf)?; + msg_buf.truncate(msg_len); + + let handshake_hash = get_handshake_hash(&noise)?; + + let noise = noise.into_transport_mode()?; + + Ok((noise, msg_buf, handshake_hash)) + } + + #[allow(clippy::type_complexity)] + fn establish_connection(mut noise: snow::HandshakeState, encrypted_msg_data: &[u8]) + -> Result<(snow::TransportState, Vec, HandshakeHash, HandshakeHash), snow::Error> { + let their_handshake_hash = get_handshake_hash(&noise)?; + + let mut payload_buf = vec![0; encrypted_msg_data.len()]; + let payload_len = noise.read_message(encrypted_msg_data, &mut payload_buf)?; + payload_buf.truncate(payload_len); + + let final_handshake_hash = get_handshake_hash(&noise)?; + + let noise = noise.into_transport_mode()?; + Ok((noise, payload_buf, their_handshake_hash, final_handshake_hash)) + } + + pub fn recv(&mut self, msg_data: &[u8]) -> Result { + let mut shared_ref = self.shared.as_ref().borrow_mut(); + let shared = &mut *shared_ref; + match &mut shared.session { + session @ SessionState::Disconnected | + session @ SessionState::WaitingForAttestation { .. } | + session @ SessionState::Connected { .. } | + session @ SessionState::Accepted { .. } => { + warn!("dropping message from {} in {} state", self.remote_node_id, session); + Err(RemoteRecvError::InvalidState) + } + session @ SessionState::Initiated { .. } => { + match PeerConnectReply::decode(msg_data) { + Ok(connect_reply) => { + let noise = match std::mem::replace(session, SessionState::Disconnected) { + SessionState::Initiated { noise } => noise, + _ => unreachable!(), + }; + match Self::establish_connection(noise, &connect_reply.noise_data) { + Ok((noise, _payload, their_handshake_hash, final_handshake_hash)) => { + *session = SessionState::Connected { noise, their_handshake_hash, final_handshake_hash }; + let sgx_quote = connect_reply.sgx_quote; + Err(RemoteRecvError::NeedsAttestation(GetAttestationRequest { + request_id: self.remote_node_id.to_vec(), + sgx_quote, + })) + } + Err(err) => { + warn!("error decrypting connect reply from {}: {}", self.remote_node_id, err); + Err(RemoteRecvError::DecodeError) + } + } + } + Err(err) => { + warn!("error decoding connect reply from {}: {}", self.remote_node_id, err); + Err(RemoteRecvError::DecodeError) + } + } + } + mut session @ SessionState::Responded { .. } | + mut session @ SessionState::Authorized { .. } => { + let noise = match &mut session { + SessionState::Responded { noise, .. } => noise, + SessionState::Authorized { noise, .. } => noise, + _ => static_unreachable!(), + }; + match read_noise_message(noise, &shared.noise_buffer, msg_data) { + Ok(msg_data) => { + if let SessionState::Responded { .. } = &session { + *session = match std::mem::replace(session, SessionState::Disconnected) { + SessionState::Responded { noise, attestation, handshake_hash } => + SessionState::Authorized { noise, attestation, handshake_hash }, + _ => unreachable!(), + }; + } + + match R::decode(&msg_data.get()[..]) { + Ok(reply) => { + Ok(reply) + } + Err(decode_error) => { + error!("error decoding message from {}: {}", &self.remote_node_id, decode_error); + Err(RemoteRecvError::DecodeError) + } + } + } + Err(err) => { + error!("error decrypting message from {}: {}", &self.remote_node_id, err); + Err(RemoteRecvError::DecodeError) + } + } + } + } + } +} + +impl RemoteCommon for RemoteState +where M: prost::Message + 'static, + R: prost::Message + Default + 'static +{ + fn id(&self) -> &NodeId { + &self.remote_node_id + } + fn attestation(&self) -> Option { + self.shared.as_ref().borrow_mut().attestation() + } +} + +impl Remote for RemoteState +where M: prost::Message + 'static, + R: prost::Message + Default + 'static +{ + fn connect(&mut self) -> Result<(), ()> { + if self.node_params.node_id == self.remote_node_id { + return Err(()); + } + + let mut shared = self.shared.as_ref().borrow_mut(); + let session = match &mut shared.session { + session @ SessionState::Disconnected | + session @ SessionState::WaitingForAttestation { .. } | + session @ SessionState::Initiated { .. } | + session @ SessionState::Connected { .. } => { + session + } + + SessionState::Accepted { .. } | + SessionState::Responded { .. } | + SessionState::Authorized { .. } => { + return Err(()); + } + }; + + match self.initiate_connection() { + Ok(mut noise) => { + match self.auth_type { + RemoteAuthorizationType::Mutual | RemoteAuthorizationType::SelfOnly => { + *session = SessionState::WaitingForAttestation { noise }; + } + RemoteAuthorizationType::RemoteOnly => { + match Self::connection_request(&mut noise) { + Ok(noise_data) => { + let connect_req = PeerConnectRequest { + node_type: self.node_params.node_type.into(), + ias_report: None, + noise_data, + }; + let mut connect_req_data = Vec::with_capacity(connect_req.encoded_len()); + assert!(connect_req.encode(&mut connect_req_data).is_ok()); + + kbupd_send(EnclaveMessage { + inner: Some(EnclaveMessageInner::SendMessageRequest(SendMessageRequest { + node_id: self.remote_node_id.to_vec(), + data: connect_req_data, + syn: true, + debug_msg: None, + })), + }); + *session = SessionState::Initiated { noise }; + } + Err(noise_error) => { + error!("noise error connecting to {}: {}", &self.remote_node_id, noise_error); + } + } + } + } + } + Err(noise_error) => { + error!("error initiating connection with {}: {}", self.remote_node_id, noise_error); + } + } + Ok(()) + } + + fn accept(&mut self, connect_request: PeerConnectRequest) -> Result<(), ()> { + if self.node_params.node_id == self.remote_node_id { + return Err(()); + } + + let mut shared = self.shared.as_ref().borrow_mut(); + let session = match &mut shared.session { + session @ SessionState::Disconnected | + session @ SessionState::WaitingForAttestation { .. } | + session @ SessionState::Accepted { .. } | + session @ SessionState::Responded { .. } => { + session + } + + session @ SessionState::Initiated { .. } => { + if self.node_params.node_id < self.remote_node_id { + session + } else { + warn!("dropping connect request from {} in {} state", self.remote_node_id, session); + return Err(()); + } + } + session @ SessionState::Connected { .. } | + session @ SessionState::Authorized { .. } => { + warn!("dropping connect request from {} in {} state", self.remote_node_id, session); + return Err(()); + } + }; + + match self.accept_connection(&connect_request.noise_data) { + Ok((noise, their_handshake_hash)) => { + match self.auth_type { + RemoteAuthorizationType::Mutual | RemoteAuthorizationType::RemoteOnly => { + match validate_ias_report(connect_request.ias_report.as_ref(), &their_handshake_hash.hash) { + Ok(attestation) => { + *session = SessionState::Accepted { noise, attestation: Some(attestation) }; + Ok(()) + } + Err(attestation_error) => { + warn!("attestation error accepting peer {}: {}", self.remote_node_id, attestation_error); + Err(()) + } + } + } + RemoteAuthorizationType::SelfOnly => { + *session = SessionState::Accepted { noise, attestation: None }; + Ok(()) + } + } + } + Err(noise_error) => { + error!("decrypt error accepting peer {}: {}", self.remote_node_id, noise_error); + Err(()) + } + } + } + + fn qe_info_reply(&self, reply: &GetQeInfoReply) -> Result { + let shared = self.shared.as_ref().borrow(); + + let report_data: [u8; 32] = match &shared.session { + SessionState::WaitingForAttestation { noise, .. } | + SessionState::Accepted { noise, .. } => { + match get_handshake_hash(noise) { + Ok(our_handshake_hash) => our_handshake_hash.hash, + Err(_) => return Err(()), + } + } + SessionState::Authorized { handshake_hash, .. } => { + handshake_hash.get_hash_for_node(&self.node_params.node_id) + } + _ => { + return Err(()); + } + }; + + #[allow(clippy::cast_possible_truncation)] + let qe_target_info = sgx::SgxTargetInfo { + mrenclave: &reply.mrenclave, + flags: reply.flags, + xfrm: reply.xfrm, + misc_select: reply.misc_select, + config_svn: reply.config_svn as u16, + config_id: &reply.config_id, + }; + match sgx::create_report(&qe_target_info, &report_data) { + Ok(sgx_report) => { + Ok(GetQuoteRequest { + request_id: self.remote_node_id.to_vec(), + sgx_report, + }) + } + Err(sgx_error) => { + warn!("error generating sgx report: {}", sgx_error); + Err(()) + } + } + } + + fn get_quote_reply(&mut self, reply: GetQuoteReply) -> Result, Option> { + let sgx_quote = reply.sgx_quote; + match &mut self.shared.as_ref().borrow_mut().session { + SessionState::WaitingForAttestation { .. } => { + Ok(Some(GetAttestationRequest { + request_id: self.remote_node_id.to_vec(), + sgx_quote, + })) + } + session @ SessionState::Accepted { .. } => { + let (noise, attestation) = match std::mem::replace(session, SessionState::Disconnected) { + SessionState::Accepted { noise, attestation } => (noise, attestation), + _ => unreachable!(), + }; + let (noise, noise_data, handshake_hash) = match Self::connection_response(noise) { + Ok(result) => result, + Err(noise_error) => { + error!("error accepting connection request from {}: {}", self.remote_node_id, noise_error); + return Err(None); + } + }; + *session = SessionState::Responded { noise, attestation, handshake_hash }; + + let msg = PeerConnectReply { + sgx_quote, + noise_data + }; + let mut encoded_msg_data = Vec::with_capacity(msg.encoded_len()); + assert!(msg.encode(&mut encoded_msg_data).is_ok()); + + kbupd_send(EnclaveMessage { + inner: Some(EnclaveMessageInner::SendMessageRequest(SendMessageRequest { + node_id: self.remote_node_id.to_vec(), + data: encoded_msg_data, + syn: false, + debug_msg: None, + })), + }); + + Ok(None) + } + SessionState::Authorized { .. } => { + Err(Some(EnclaveGetQuoteReply { sgx_quote })) + } + _ => Ok(None), + } + } + + fn attestation_reply(&mut self, ias_report: IasReport) -> Result, ()> { + match &mut self.shared.as_ref().borrow_mut().session { + session @ SessionState::WaitingForAttestation { .. } => { + let mut noise = match std::mem::replace(session, SessionState::Disconnected) { + SessionState::WaitingForAttestation { noise } => noise, + _ => unreachable!(), + }; + match Self::connection_request(&mut noise) { + Ok(noise_data) => { + let connect_req = PeerConnectRequest { + node_type: self.node_params.node_type.into(), + ias_report: Some(ias_report), + noise_data, + }; + let mut connect_req_data = Vec::with_capacity(connect_req.encoded_len()); + assert!(connect_req.encode(&mut connect_req_data).is_ok()); + + kbupd_send(EnclaveMessage { + inner: Some(EnclaveMessageInner::SendMessageRequest(SendMessageRequest { + node_id: self.remote_node_id.to_vec(), + data: connect_req_data, + syn: true, + debug_msg: None, + })), + }); + *session = SessionState::Initiated { noise }; + Ok(None) + } + Err(noise_error) => { + error!("noise error connecting to {}: {}", &self.remote_node_id, noise_error); + Err(()) + } + } + } + session @ SessionState::Connected { .. } => { + let (noise, their_handshake_hash, final_handshake_hash) = match std::mem::replace(session, SessionState::Disconnected) { + SessionState::Connected { noise, their_handshake_hash, final_handshake_hash } => + (noise, their_handshake_hash, final_handshake_hash), + _ => unreachable!(), + }; + match validate_ias_report(Some(&ias_report), &their_handshake_hash.hash) { + Ok(attestation) => { + let handshake_hash = final_handshake_hash; + *session = SessionState::Authorized { noise, attestation: Some(attestation), handshake_hash }; + Ok(Some(attestation)) + } + Err(attestation_error) => { + error!("error validating attestation report for {}: {}", &self.remote_node_id, attestation_error); + Err(()) + } + } + } + SessionState::Authorized { attestation, handshake_hash, .. } => { + match validate_ias_report(Some(&ias_report), &handshake_hash.get_hash_for_node(&self.remote_node_id)) { + Ok(new_attestation) => { + verbose!("validated attestation report for {}: {}", &self.remote_node_id, &new_attestation); + *attestation = Some(new_attestation); + Ok(None) + } + Err(attestation_error) => { + error!("error validating attestation report for {}: {}", &self.remote_node_id, attestation_error); + Err(()) + } + } + } + _ => { + Err(()) + } + } + } +} + +impl RemoteMessageSender for RemoteState +where M: prost::Message + 'static, + R: prost::Message + Default + 'static, +{ + type Message = M; + fn send(&self, message: Rc) -> Result<(), ()> { + self.shared.as_ref().borrow_mut().send(message) + } +} + +impl fmt::Display for RemoteState +where M: prost::Message + 'static, + R: prost::Message + Default + 'static, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("RemoteState") + .field(&self.remote_node_id) + .field(&self.remote_type) + .finish() + } +} + +fn get_handshake_hash(noise: &snow::HandshakeState) -> Result { + let handshake_hash_slice = noise.get_handshake_hash(); + let mut hash = [0u8; 32]; + hash.copy_from_slice(handshake_hash_slice); + Ok(HandshakeHash { hash }) +} + +fn write_noise_message(noise: &mut snow::TransportState, noise_buffers: &SharedNoiseBuffers, payload: &[u8]) -> Result, snow::Error> { + let mut noise_buffer_ref = RefCell::borrow_mut(&noise_buffers.inner.write_buffer); + let chunk_buffer = &mut noise_buffer_ref.0; + + let payload_chunks = payload.chunks(65519); + let encrypted_msg_buf_len = payload_chunks.len().saturating_mul(NOISE_CHUNK_MAX_LENGTH); + let mut encrypted_msg_buf = Vec::with_capacity(encrypted_msg_buf_len); + for payload_chunk in payload_chunks { + let encrypted_chunk_len = noise.write_message(payload_chunk, chunk_buffer)?; + let encrypted_chunk_buf = chunk_buffer.get_mut(..encrypted_chunk_len).unwrap_or_else(|| unreachable!()); + encrypted_msg_buf.extend_from_slice(encrypted_chunk_buf); + noise.rekey_outgoing(); + } + Ok(encrypted_msg_buf) +} + +fn read_noise_message(noise: &mut snow::TransportState, shared_noise_buffers: &SharedNoiseBuffers, encrypted: &[u8]) -> Result>, snow::Error> { + let mut noise_buffer = shared_noise_buffers.inner.read_buffer.take().unwrap_or_default(); + match read_noise_message_with_buffer(noise, &mut noise_buffer.get_mut().0, encrypted) { + Ok(msg_data) => { + noise_buffer.clear_to(msg_data.get().len()); + shared_noise_buffers.inner.read_buffer.set(Some(noise_buffer)); + Ok(msg_data) + } + Err(error) => { + noise_buffer.clear(); + shared_noise_buffers.inner.read_buffer.set(Some(noise_buffer)); + Err(error) + } + } +} + +fn read_noise_message_with_buffer(noise: &mut snow::TransportState, chunk_buffer: &mut [u8; NOISE_CHUNK_MAX_LENGTH], encrypted: &[u8]) -> Result>, snow::Error> { + let encrypted_chunks = encrypted.chunks(NOISE_CHUNK_MAX_LENGTH); + let msg_buf_len = encrypted_chunks.len().saturating_mul(65519); + let mut msg_buf = SecretValue::new(Vec::with_capacity(msg_buf_len)); + for encrypted_chunk in encrypted_chunks { + let decrypted_chunk_len = noise.read_message(encrypted_chunk, chunk_buffer)?; + let decrypted_chunk_buf = chunk_buffer.get_mut(..decrypted_chunk_len).unwrap_or_else(|| unreachable!()); + msg_buf.get_mut().extend_from_slice(decrypted_chunk_buf); + noise.rekey_incoming(); + } + Ok(msg_buf) +} + +impl fmt::Display for SessionState { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SessionState::Disconnected => write!(fmt, "Disconnected"), + SessionState::WaitingForAttestation { .. } => write!(fmt, "WaitingForAttestation"), + SessionState::Initiated { .. } => write!(fmt, "Initiated"), + SessionState::Connected { .. } => write!(fmt, "Connected"), + SessionState::Accepted { .. } => write!(fmt, "Accepted"), + SessionState::Responded { .. } => write!(fmt, "Responded"), + SessionState::Authorized { .. } => write!(fmt, "Authorized"), + } + } +} + +static IAS_TRUST_ANCHORS: &webpki::TLSServerTrustAnchors<'_> = &webpki::TLSServerTrustAnchors(&[ + webpki::TrustAnchor { + subject: &[49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 83, 49, 11, 48, 9, 6, 3, 85, 4, 8, 12, 2, 67, 65, 49, 20, 48, 18, 6, 3, 85, 4, 7, 12, 11, 83, 97, 110, 116, 97, 32, 67, 108, 97, 114, 97, 49, 26, 48, 24, 6, 3, 85, 4, 10, 12, 17, 73, 110, 116, 101, 108, 32, 67, 111, 114, 112, 111, 114, 97, 116, 105, 111, 110, 49, 48, 48, 46, 6, 3, 85, 4, 3, 12, 39, 73, 110, 116, 101, 108, 32, 83, 71, 88, 32, 65, 116, 116, 101, 115, 116, 97, 116, 105, 111, 110, 32, 82, 101, 112, 111, 114, 116, 32, 83, 105, 103, 110, 105, 110, 103, 32, 67, 65], + spki: &[48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 143, 0, 48, 130, 1, 138, 2, 130, 1, 129, 0, 159, 60, 100, 126, 181, 119, 60, 187, 81, 45, 39, 50, 192, 215, 65, 94, 187, 85, 160, 250, 158, 222, 46, 100, 145, 153, 230, 130, 29, 185, 16, 213, 49, 119, 55, 9, 119, 70, 106, 106, 94, 71, 134, 204, 210, 221, 235, 212, 20, 157, 106, 47, 99, 37, 82, 157, 209, 12, 201, 135, 55, 176, 119, 156, 26, 7, 226, 156, 71, 161, 174, 0, 73, 72, 71, 108, 72, 159, 69, 165, 161, 93, 122, 200, 236, 198, 172, 198, 69, 173, 180, 61, 135, 103, 157, 245, 156, 9, 59, 197, 162, 233, 105, 108, 84, 120, 84, 27, 151, 158, 117, 75, 87, 57, 20, 190, 85, 211, 47, 244, 192, 157, 223, 39, 33, 153, 52, 205, 153, 5, 39, 179, 249, 46, 215, 143, 191, 41, 36, 106, 190, 203, 113, 36, 14, 243, 156, 45, 113, 7, 180, 71, 84, 90, 127, 251, 16, 235, 6, 10, 104, 169, 133, 128, 33, 158, 54, 145, 9, 82, 104, 56, 146, 214, 165, 226, 168, 8, 3, 25, 62, 64, 117, 49, 64, 78, 54, 179, 21, 98, 55, 153, 170, 130, 80, 116, 64, 151, 84, 162, 223, 232, 245, 175, 213, 254, 99, 30, 31, 194, 175, 56, 8, 144, 111, 40, 167, 144, 217, 221, 159, 224, 96, 147, 155, 18, 87, 144, 197, 128, 93, 3, 125, 245, 106, 153, 83, 27, 150, 222, 105, 222, 51, 237, 34, 108, 193, 32, 125, 16, 66, 181, 201, 171, 127, 64, 79, 199, 17, 192, 254, 71, 105, 251, 149, 120, 177, 220, 14, 196, 105, 234, 26, 37, 224, 255, 153, 20, 136, 110, 242, 105, 155, 35, 91, 180, 132, 125, 214, 255, 64, 182, 6, 230, 23, 7, 147, 194, 251, 152, 179, 20, 88, 127, 156, 253, 37, 115, 98, 223, 234, 177, 11, 59, 210, 217, 118, 115, 161, 164, 189, 68, 196, 83, 170, 244, 127, 193, 242, 211, 208, 243, 132, 247, 74, 6, 248, 156, 8, 159, 13, 166, 205, 183, 252, 238, 232, 201, 130, 26, 142, 84, 242, 92, 4, 22, 209, 140, 70, 131, 154, 95, 128, 18, 251, 221, 61, 199, 77, 37, 98, 121, 173, 194, 192, 213, 90, 255, 111, 6, 34, 66, 93, 27, 2, 3, 1, 0, 1], + name_constraints: None, + }, +]); +static IAS_CHAIN_ALGOS: &'static [&webpki::SignatureAlgorithm] = &[ + &webpki::RSA_PKCS1_2048_8192_SHA256, + &webpki::RSA_PKCS1_2048_8192_SHA384, + &webpki::RSA_PKCS1_2048_8192_SHA512, + &webpki::RSA_PKCS1_3072_8192_SHA384, + &webpki::ECDSA_P256_SHA256, + &webpki::ECDSA_P256_SHA384, + &webpki::ECDSA_P384_SHA256, + &webpki::ECDSA_P384_SHA384, +]; + +#[derive(Debug)] +enum AttestationVerificationError { + NoAttestationReport, + InvalidJson(serde_json::Error), + InvalidCertificate(webpki::Error), + InvalidSignature(webpki::Error), + WrongVersion(u64), + InvalidTimestamp(String), + StaleRevocationList, + InvalidQuote(SgxQuoteDecodeError), + #[cfg(not(feature = "insecure"))] + IsDebugQuote, + InvalidQuoteReportData, + InvalidMrenclave([u8; 32]), + CreateReportError(u32), + AttestationError(String), +} + +fn validate_ias_report(maybe_ias_report: Option<&IasReport>, + expected_report_data: &[u8]) + -> Result { + #[cfg(feature = "insecure")] { + if maybe_ias_report.is_none() { + return Ok(AttestationParameters { unix_timestamp_seconds: 0 }); + } + } + + let ias_report = match maybe_ias_report { + Some(ias_report) => ias_report, + None => { + return Err(AttestationVerificationError::NoAttestationReport); + } + }; + + let body: IasReportBody = serde_json::from_slice(&ias_report.body[..]) + .map_err(AttestationVerificationError::InvalidJson)?; + + if body.version != 3 { + return Err(AttestationVerificationError::WrongVersion(body.version)); + } + + match body.isvEnclaveQuoteStatus.as_str() { + "OK" => { + } + #[cfg(feature = "insecure")] + "GROUP_OUT_OF_DATE" | "CONFIGURATION_NEEDED" => { + } + "SIGRL_VERSION_MISMATCH" => { + return Err(AttestationVerificationError::StaleRevocationList); + } + _ => { + return Err(AttestationVerificationError::AttestationError(body.isvEnclaveQuoteStatus)); + } + } + + let quote = SgxQuote::decode(&mut &body.isvEnclaveQuoteBody[..]) + .map_err(AttestationVerificationError::InvalidQuote)?; + + if "e.report_data.0[0..32] != expected_report_data { + return Err(AttestationVerificationError::InvalidQuoteReportData); + } + + let our_report = sgx::create_report_raw(None, &[0; 64]) + .map_err(AttestationVerificationError::CreateReportError)?; + if quote.mrenclave != our_report.body.mr_enclave.m { + return Err(AttestationVerificationError::InvalidMrenclave(quote.mrenclave)); + } + + if quote.is_debug_quote() { + #[cfg(not(feature = "insecure"))] { + return Err(AttestationVerificationError::IsDebugQuote); + } + } + + let unix_timestamp_seconds = (NaiveDateTime::parse_from_str(&body.timestamp, "%Y-%m-%dT%H:%M:%S.%f").ok()) + .map(|naive_datetime: NaiveDateTime| DateTime::from_utc(naive_datetime, Utc)) + .and_then(|utc_datetime: DateTime| utc_datetime.timestamp().to_u64()) + .ok_or_else(|| AttestationVerificationError::InvalidTimestamp(body.timestamp))?; + let certificate = (ias_report.certificates.get(0).ok_or(webpki::Error::BadDER)) + .and_then(|certificate: &Vec| webpki::EndEntityCert::from(certificate)) + .map_err(AttestationVerificationError::InvalidCertificate)?; + let chain = (ias_report.certificates.get(1..).unwrap_or_default().iter()) + .map(|cert: &Vec| &cert[..]) + .collect::>(); + certificate.verify_is_valid_tls_server_cert(IAS_CHAIN_ALGOS, IAS_TRUST_ANCHORS, &chain, webpki::Time::from_seconds_since_unix_epoch(unix_timestamp_seconds)) + .map_err(AttestationVerificationError::InvalidCertificate)?; + certificate.verify_signature(&webpki::RSA_PKCS1_2048_8192_SHA256, &ias_report.body, &ias_report.signature) + .map_err(AttestationVerificationError::InvalidSignature)?; + Ok(AttestationParameters { unix_timestamp_seconds }) +} + +#[allow(non_snake_case)] +#[derive(Deserialize)] +pub struct IasReportBody { + pub isvEnclaveQuoteStatus: String, + + #[serde(deserialize_with = "deserialize_base64")] + pub isvEnclaveQuoteBody: Vec, + + pub version: u64, + + pub timestamp: String, +} + +impl fmt::Display for AttestationVerificationError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, fmt) + } +} + +// +// NodeId impls +// + +impl> From for NodeId { + fn from(from: T) -> Self { + let from = from.as_ref(); + if from.len() == NODE_ID_LEN { + let mut id = [0; NODE_ID_LEN]; + id.copy_from_slice(from); + NodeId::Valid(id) + } else { + NodeId::Invalid(from.to_vec()) + } + } +} + +impl fmt::Display for NodeId { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&util::ToHex(self), fmt) + } +} +impl fmt::Debug for NodeId { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +impl Deref for NodeId { + type Target = [u8]; + fn deref(&self) -> &[u8] { + match self { + NodeId::Valid(id) => id, + NodeId::Invalid(id) => id, + } + } +} + +// +// NodeParams impls +// + +impl NodeParams { + pub fn generate(node_type: NodeType) -> Self { + let params = NOISE_PARAMS.parse().unwrap_or_else(|_| unreachable!()); + let builder = snow::Builder::with_resolver(params, Box::new(SnowResolver)); + let keypair = builder.generate_keypair().unwrap_or_else(|_| unreachable!()); + assert_eq!(keypair.public.len(), 32); + Self { + node_key: keypair.private.into(), + node_id: keypair.public.into(), + node_type, + } + } + pub fn node_id(&self) -> &NodeId { + &self.node_id + } +} + +// +// NodeType impls +// + +impl fmt::Display for NodeType { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + NodeType::None => write!(fmt, "none"), + NodeType::Replica => write!(fmt, "replica"), + NodeType::Frontend => write!(fmt, "frontend"), + } + } +} + +// +// HandshakeHash impls +// + +impl HandshakeHash { + fn get_hash_for_node(&self, node_id: &NodeId) -> [u8; 32] { + let mut hasher = SHA256Context::default(); + let mut output = [0u8; 32]; + hasher.update(&self.hash); + hasher.update(node_id); + hasher.result(&mut output); + output + } +} + +// +// AttestationParameters impls +// + +impl AttestationParameters { + pub fn new(unix_timestamp: Duration) -> Self { + Self { + unix_timestamp_seconds: unix_timestamp.as_secs(), + } + } +} + +// +// Shared impls +// + +impl Shared +where M: prost::Message + 'static, +{ + fn attestation(&self) -> Option { + match &self.session { + SessionState::Authorized { attestation, .. } => *attestation, + _ => None, + } + } + + pub fn send(&mut self, msg: impl Borrow) -> Result<(), ()> { + match &mut self.session { + SessionState::Authorized { noise, .. } => { + let mut encoded_msg_data = SecretValue::new(Vec::with_capacity(msg.borrow().encoded_len())); + assert!(msg.borrow().encode(encoded_msg_data.get_mut()).is_ok()); + match write_noise_message(noise, &self.noise_buffer, encoded_msg_data.get()) { + Ok(encrypted_msg_data) => { + #[allow(unused_assignments, unused_mut)] + let mut debug_msg = None; + #[cfg(feature = "insecure")] + #[cfg(feature = "trace")] + { + debug_msg = Some(format!("{:?}", msg.borrow()).into()); + } + kbupd_send(EnclaveMessage { + inner: Some(EnclaveMessageInner::SendMessageRequest(SendMessageRequest { + node_id: self.remote_node_id.to_vec(), + debug_msg, + data: encrypted_msg_data, + syn: false, + })) + }); + Ok(()) + } + Err(err) => { + error!("unexpected error encrypting message to {}: {}", &self.remote_node_id, err); + Err(()) + } + } + } + _ => { + verbose!("dropped message to remote {} in {} state", &self.remote_node_id, &self.session); + Err(()) + } + } + } +} + +// +// NoiseBuffer impls +// + +impl Default for NoiseBuffer { + fn default() -> Self { + Self([0; NOISE_CHUNK_MAX_LENGTH]) + } +} + +impl AsMut<[u8]> for Box { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +// +// RemoteSender impls +// + +impl RemoteCommon for RemoteSender +where M: prost::Message + 'static, +{ + fn id(&self) -> &NodeId { + &self.id + } + fn attestation(&self) -> Option { + self.shared.as_ref().borrow_mut().attestation() + } +} + +impl RemoteMessageSender for RemoteSender +where M: prost::Message + 'static, +{ + type Message = M; + fn send(&self, message: Rc) -> Result<(), ()> { + self.shared.as_ref().borrow_mut().send(message) + } +} + +impl fmt::Display for RemoteSender +where M: prost::Message + 'static, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.id(), fmt) + } +} + +impl Clone for RemoteSender +where M: prost::Message + 'static, +{ + fn clone(&self) -> Self { + Self { + id: self.id.clone(), + shared: Rc::clone(&self.shared), + } + } +} + +// +// AttestationParameters impls +// + +impl Copy for AttestationParameters {} +impl Eq for AttestationParameters {} +impl PartialOrd for AttestationParameters { + fn partial_cmp(&self, other: &Self) -> Option { + Some(Ord::cmp(self, other)) + } +} +impl Ord for AttestationParameters { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.unix_timestamp_seconds.cmp(&other.unix_timestamp_seconds) + } +} +impl fmt::Display for AttestationParameters { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} diff --git a/enclave/kbupd_enclave/src/remote/peer_manager.rs b/enclave/kbupd_enclave/src/remote/peer_manager.rs new file mode 100644 index 0000000..3bc3fa1 --- /dev/null +++ b/enclave/kbupd_enclave/src/remote/peer_manager.rs @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::collections::*; +use std::rc::*; + +use hashbrown::{HashMap, hash_map}; +use prost::{Message}; +use rand_core::{RngCore}; +use sgxsd_ffi::{RdRand}; + +use crate::{kbupd_send}; +use crate::hasher::{DefaultHasher}; +use crate::protobufs::kbupd::*; +use crate::protobufs::kbupd_enclave::*; +use crate::remote::*; + +pub struct PeerManager { + node_params: Rc, + noise_buffers: SharedNoiseBuffers, + connecting_peers: BTreeSet, + qe_info_req: QeInfoRequestState, + peers: HashMap, DefaultHasher>, + total_ticks: u32, +} + +pub struct PeerStarter<'a,T,U> { + peer_entry: hash_map::VacantEntry<'a, NodeId, Option, DefaultHasher>, + connecting_peers: &'a mut BTreeSet, + connecting_peer: ConnectingPeerState, + remote: U, +} + +pub struct PeerAcceptor<'a,T> { + peer_entry: hash_map::VacantEntry<'a, NodeId, Option, DefaultHasher>, + node_params: Rc, + noise_buffers: SharedNoiseBuffers, + remote_node_type: NodeType, + qe_info_req: &'a mut QeInfoRequestState, + connect_request: PeerConnectRequest, +} + +pub trait Peer { + type Message; + fn remote_mut(&mut self) -> &mut dyn Remote; + fn recv(&mut self, msg_data: &[u8]) -> Result; + fn send_quote_reply(&mut self, sgx_quote: EnclaveGetQuoteReply) -> Result<(), ()>; +} + +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd)] +struct ConnectingPeerState { + next_timeout_tick: u32, + last_interval_ticks: u32, + node_id: NodeId, +} + +enum QeInfoRequestState { + None, + Sent { + needs_qe_info: Vec, + ticks_elapsed: u32, + }, +} + +impl PeerManager +where T: Peer +{ + pub fn new(node_type: NodeType) -> Self { + Self { + node_params: Rc::new(NodeParams::generate(node_type)), + noise_buffers: Default::default(), + connecting_peers: Default::default(), + qe_info_req: QeInfoRequestState::None, + peers: Default::default(), + total_ticks: Default::default(), + } + } + + pub fn our_node_id(&self) -> &NodeId { + self.node_params.node_id() + } + + pub fn get_peer(&self, node_id: &NodeId) -> Option<&T> { + let peer = self.peers.get(node_id)?.as_ref()?; + Some(peer) + } + + pub fn get_peer_mut(&mut self, node_id: &NodeId) -> Option<&mut T> { + let peer = self.peers.get_mut(node_id)?.as_mut()?; + Some(peer) + } + + pub fn remove_peer(&mut self, node_id: &NodeId) { + if let hash_map::Entry::Occupied(mut peer_entry) = self.peers.entry(node_id.clone()) { + if let Some(peer) = peer_entry.get_mut() { + if let Some(_) = peer.remote_mut().attestation() { + *peer_entry.get_mut() = None; + } else { + peer_entry.remove(); + } + } + } + } + + pub fn timer_tick(&mut self, min_timeout_ticks: u32, max_timeout_ticks: u32) { + self.total_ticks = self.total_ticks.wrapping_add(1); + + if let QeInfoRequestState::Sent { ticks_elapsed, .. } = &mut self.qe_info_req { + *ticks_elapsed = ticks_elapsed.saturating_add(1); + if *ticks_elapsed >= min_timeout_ticks { + Self::send_get_qe_info_request(); + *ticks_elapsed = Default::default(); + } + } + + let mut new_connecting_peers = BTreeSet::new(); + while let Some(mut connecting_peer) = self.take_connecting_peer() { + if let Some(peer) = self.peers.get_mut(&connecting_peer.node_id).and_then(Option::as_mut) { + let last_interval_ticks = connecting_peer.last_interval_ticks; + let half_interval_ticks = last_interval_ticks.min(max_timeout_ticks / 2) + .max(min_timeout_ticks); + let rand_interval_ticks = RdRand.next_u32().checked_rem(half_interval_ticks) + .unwrap_or(0); + let next_timeout_ticks = half_interval_ticks.saturating_add(rand_interval_ticks); + + connecting_peer.last_interval_ticks = half_interval_ticks.saturating_add(half_interval_ticks); + connecting_peer.next_timeout_tick = next_timeout_ticks.saturating_add(self.total_ticks.wrapping_add(1)); + match peer.remote_mut().connect() { + Ok(()) => { + info!("connecting to peer {} with retry in {} ticks, next interval {} ticks", + peer.remote_mut().id(), next_timeout_ticks, connecting_peer.last_interval_ticks); + Self::get_qe_info(&mut self.qe_info_req, peer.remote_mut().id().clone()); + new_connecting_peers.insert(connecting_peer); + } + Err(()) => (), + } + } + } + self.connecting_peers.append(&mut new_connecting_peers); + } + + fn take_connecting_peer(&mut self) -> Option { + if let Some(connecting_peer) = self.connecting_peers.iter().next() { + if connecting_peer.next_timeout_tick <= self.total_ticks { + let connecting_peer = connecting_peer.clone(); + self.connecting_peers.remove(&connecting_peer); + Some(connecting_peer) + } else { + None + } + } else { + None + } + } + + pub fn start_peer<'a,M,R>(&'a mut self, + peer_node_id: NodeId, + peer_node_type: NodeType, + auth_type: RemoteAuthorizationType) + -> Result>, Option<&'a mut T>> + where M: prost::Message + 'static, + R: prost::Message + Default + 'static, + { + match self.peers.entry(peer_node_id) { + hash_map::Entry::Occupied(peer_entry) => { + Err(peer_entry.into_mut().as_mut()) + } + hash_map::Entry::Vacant(peer_entry) => { + let remote = RemoteState::new(Rc::clone(&self.node_params), peer_entry.key().clone(), peer_node_type, auth_type, self.noise_buffers.clone()); + let connecting_peer = ConnectingPeerState { + next_timeout_tick: self.total_ticks.wrapping_add(1), + last_interval_ticks: 0, + node_id: remote.id().clone(), + }; + Ok(PeerStarter { + remote, + peer_entry, + connecting_peers: &mut self.connecting_peers, + connecting_peer, + }) + } + } + } + + pub fn request_quote(&mut self, peer_node_id: NodeId) { + Self::get_qe_info(&mut self.qe_info_req, peer_node_id); + } + + fn get_qe_info(qe_info_req: &mut QeInfoRequestState, peer_node_id: NodeId) { + if let QeInfoRequestState::None = qe_info_req { + info!("requesting qe_info to generate quote for {}", peer_node_id); + Self::send_get_qe_info_request(); + *qe_info_req = QeInfoRequestState::Sent { + needs_qe_info: Default::default(), + ticks_elapsed: Default::default(), + }; + } + if let QeInfoRequestState::Sent { needs_qe_info, .. } = qe_info_req { + needs_qe_info.push(peer_node_id); + } else { + static_unreachable!(); + } + } + + fn send_get_qe_info_request() { + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::GetQeInfoRequest(GetQeInfoRequest {})), + }); + } + + pub fn get_qe_info_reply(&mut self, get_qe_info_reply: GetQeInfoReply) { + if let QeInfoRequestState::Sent { needs_qe_info, .. } = + std::mem::replace(&mut self.qe_info_req, QeInfoRequestState::None) + { + info!("generating quotes for {} peers", needs_qe_info.len()); + for peer_node_id in needs_qe_info { + if let Some(peer) = self.peers.get_mut(&peer_node_id).and_then(Option::as_mut) { + match peer.remote_mut().qe_info_reply(&get_qe_info_reply) { + Ok(get_quote_request) => { + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::GetQuoteRequest(get_quote_request)), + }); + } + Err(()) => (), + } + } + } + } + } + + pub fn get_quote_reply(&mut self, get_quote_reply: GetQuoteReply) { + let peer_node_id = NodeId::from(&get_quote_reply.request_id); + if let Some(peer) = self.peers.get_mut(&peer_node_id).and_then(Option::as_mut) { + match peer.remote_mut().get_quote_reply(get_quote_reply) { + Ok(Some(get_attestation_request)) => { + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::GetAttestationRequest(get_attestation_request)), + }); + } + Err(Some(enclave_get_quote_reply)) => { + let _ignore = peer.send_quote_reply(enclave_get_quote_reply); + } + Ok(None) => (), + Err(None) => (), + } + } + } + + pub fn request_attestation(&mut self, sgx_quote: Vec, peer_node_id: NodeId) { + info!("fetching attestation for peer {}", &peer_node_id); + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::GetAttestationRequest(GetAttestationRequest { + request_id: peer_node_id.to_vec(), + sgx_quote, + })), + }); + } + + #[must_use] + pub fn get_attestation_reply(&mut self, get_attestation_reply: GetAttestationReply) -> Option<(&mut T, AttestationParameters)> { + let peer_node_id: NodeId = get_attestation_reply.request_id.into(); + let peer = self.peers.get_mut(&peer_node_id)?.as_mut()?; + match peer.remote_mut().attestation_reply(get_attestation_reply.ias_report) { + Ok(Some(attestation)) => Some((peer, attestation)), + Ok(None) => None, + Err(()) => None, + } + } + + pub fn new_message_signal(&mut self, message: NewMessageSignal) -> Result::Message)>, PeerAcceptor<'_,T>> { + let peer_node_id: NodeId = message.node_id.into(); + + if message.syn { + let connect_request = match PeerConnectRequest::decode(&message.data[..]) { + Ok(connect_request) => connect_request, + Err(decode_error) => { + warn!("dropping connect request from {}: {}", &peer_node_id, decode_error); + return Ok(None); + } + }; + match self.peer_connect_request(connect_request, peer_node_id) { + Some(peer_acceptor) => Err(peer_acceptor), + None => Ok(None), + } + } else { + match self.peer_message(message.data, peer_node_id) { + Ok(result) => Ok(result), + Err(()) => Ok(None), + } + } + } + + fn peer_message(&mut self, message_data: Vec, peer_node_id: NodeId) -> Result::Message)>, ()> { + let peer_entry = self.peers.get_mut(&peer_node_id); + if let Some(Some(peer)) = peer_entry { + match peer.recv(&message_data) { + Ok(message) => { + Ok(Some((peer, message))) + } + Err(RemoteRecvError::NeedsAttestation(get_attestation_request)) => { + info!("fetching attestation for peer {}", &peer_node_id); + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::GetAttestationRequest(get_attestation_request)), + }); + Ok(None) + } + Err(RemoteRecvError::DecodeError) | + Err(RemoteRecvError::InvalidState) => { + Err(()) + } + } + } else if let Some(None) = peer_entry { + warn!("dropping message from evicted peer {}", &peer_node_id); + Err(()) + } else { + error!("dropping message from missing peer {}", &peer_node_id); + Err(()) + } + } + + fn peer_connect_request(&mut self, connect_request: PeerConnectRequest, peer_node_id: NodeId) -> Option> { + match self.peers.entry(peer_node_id) { + hash_map::Entry::Occupied(mut peer_entry) => { + if let Some(peer) = peer_entry.get_mut().as_mut() { + match peer.remote_mut().accept(connect_request) { + Ok(()) => { + Self::get_qe_info(&mut self.qe_info_req, peer_entry.key().clone()); + } + Err(()) => (), + } + } else { + warn!("dropping connect request from evicted peer {}", peer_entry.key()); + } + None + } + hash_map::Entry::Vacant(peer_entry) => { + if let Some(remote_node_type) = NodeType::from_i32(connect_request.node_type) { + Some(PeerAcceptor { + peer_entry, + node_params: Rc::clone(&self.node_params), + noise_buffers: self.noise_buffers.clone(), + remote_node_type, + qe_info_req: &mut self.qe_info_req, + connect_request, + }) + } else { + warn!("dropping connect request from {}: invalid node type {}", + peer_entry.key(), connect_request.node_type); + None + } + } + } + } +} + +// +// PeerStarter impls +// + +impl<'a,T,U> PeerStarter<'a,T,U> +where T: Peer, + U: Remote +{ + pub fn remote(&self) -> &U { + &self.remote + } + pub fn connect(mut self, mapper: F) -> Result<&'a mut T, (Self, F)> + where F: FnOnce(U) -> T, + { + match self.remote.connect() { + Ok(()) => { + self.connecting_peers.insert(self.connecting_peer); + let peer = self.peer_entry.insert(Some(mapper(self.remote))); + Ok(peer.as_mut().unwrap_or_else(|| unreachable!())) + } + Err(()) => { + Err((self, mapper)) + } + } + } + pub fn insert(self, mapper: impl FnOnce(U) -> T) -> &'a mut T { + self.peer_entry.insert(Some(mapper(self.remote))) + .as_mut() + .unwrap_or_else(|| unreachable!()) + } +} + +// +// PeerAcceptor impls +// + +impl<'a,T> PeerAcceptor<'a,T> +where T: Peer +{ + pub fn node_id(&self) -> &NodeId { + self.peer_entry.key() + } + pub fn connect_request(&self) -> &PeerConnectRequest { + &self.connect_request + } + pub fn accept(self, mapper: impl FnOnce(RemoteState) -> T, auth_type: RemoteAuthorizationType) -> Result<&'a mut T, ()> + where M: prost::Message + 'static, + R: prost::Message + Default + 'static, + { + let mut remote = RemoteState::new(Rc::clone(&self.node_params), self.peer_entry.key().clone(), self.remote_node_type, auth_type, self.noise_buffers); + remote.accept(self.connect_request)?; + PeerManager::::get_qe_info(self.qe_info_req, self.peer_entry.key().clone()); + let peer = self.peer_entry.insert(Some(mapper(remote))); + Ok(peer.as_mut().unwrap_or_else(|| unreachable!())) + } +} diff --git a/enclave/kbupd_enclave/src/remote/sgx_quote.rs b/enclave/kbupd_enclave/src/remote/sgx_quote.rs new file mode 100644 index 0000000..38fc95a --- /dev/null +++ b/enclave/kbupd_enclave/src/remote/sgx_quote.rs @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use bytes::{Buf}; +use num_traits::{ToPrimitive}; + +const SGX_FLAGS_INITTED: u64 = 0x0000_0000_0000_0001; +const SGX_FLAGS_DEBUG: u64 = 0x0000_0000_0000_0002; +const SGX_FLAGS_MODE64BIT: u64 = 0x0000_0000_0000_0004; +const SGX_FLAGS_RESERVED: u64 = 0xFFFF_FFFF_FFFF_FFC8; +const SGX_XFRM_RESERVED: u64 = 0xFFFF_FFFF_FFFF_FFF8; + +#[derive(Default)] +pub struct SgxQuote { + pub version: u16, + pub is_sig_linkable: bool, + pub gid: u32, + pub qe_svn: u16, + pub pce_svn: u16, + pub basename: [u8; 32], + pub cpu_svn: [u8; 16], + pub flags: u64, + pub xfrm: u64, + pub mrenclave: [u8; 32], + pub mrsigner: [u8; 32], + pub isv_prod_id: u16, + pub isv_svn: u16, + pub report_data: SgxReportData, + pub has_signature: bool, +} + +pub struct SgxReportData(pub [u8; 64]); + +impl Default for SgxReportData { + fn default() -> Self { + Self([0; 64]) + } +} + +#[derive(Debug)] +pub enum SgxQuoteDecodeError { + Truncated(usize), + NonZeroReserved(usize), + UnknownVersion(u16), + UnknownSignType(u16), + InvalidFlags(u64), + InvalidXfrm(u64), + InvalidSignatureLength(u32), +} + +impl SgxQuote { + pub fn decode(quote_buf: &mut impl Buf) -> Result { + if quote_buf.remaining() < 432 { + return Err(SgxQuoteDecodeError::Truncated(quote_buf.remaining())); + } + + let mut quote: Self = Default::default(); + + quote.version = quote_buf.get_u16_le(); + if !(quote.version >= 1 && quote.version <= 2) { + return Err(SgxQuoteDecodeError::UnknownVersion(quote.version)); + } + + let sign_type = quote_buf.get_u16_le(); + if (sign_type & !1) != 0 { + return Err(SgxQuoteDecodeError::UnknownSignType(sign_type)); + } + + quote.is_sig_linkable = sign_type == 1; + quote.gid = quote_buf.get_u32_le(); + quote.qe_svn = quote_buf.get_u16_le(); + + if quote.version > 1 { + quote.pce_svn = quote_buf.get_u16_le(); + } else { + Self::read_zero(quote_buf, 10, 2)?; + quote.pce_svn = 0; + } + + Self::read_zero(quote_buf, 12, 4)?; // xeid (reserved) + quote_buf.copy_to_slice(&mut quote.basename); + + // + // report_body + // + + quote_buf.copy_to_slice(&mut quote.cpu_svn); + Self::read_zero(quote_buf, 64, 4)?; // misc_select (reserved) + Self::read_zero(quote_buf, 68, 28)?; // reserved1 + + quote.flags = quote_buf.get_u64_le(); + if ((quote.flags & SGX_FLAGS_RESERVED ) != 0 || + (quote.flags & SGX_FLAGS_INITTED ) == 0 || + (quote.flags & SGX_FLAGS_MODE64BIT) == 0) + { + return Err(SgxQuoteDecodeError::InvalidFlags(quote.flags)); + } + + quote.xfrm = quote_buf.get_u64_le(); + if (quote.xfrm & SGX_XFRM_RESERVED) != 0 { + return Err(SgxQuoteDecodeError::InvalidXfrm(quote.xfrm)); + } + + quote_buf.copy_to_slice(&mut quote.mrenclave); + Self::read_zero(quote_buf, 144, 32)?; // reserved2 + quote_buf.copy_to_slice(&mut quote.mrsigner); + Self::read_zero(quote_buf, 208, 96)?; // reserved3 + quote.isv_prod_id = quote_buf.get_u16_le(); + quote.isv_svn = quote_buf.get_u16_le(); + Self::read_zero(quote_buf, 308, 60)?; // reserved4 + quote_buf.copy_to_slice(&mut quote.report_data.0); + + // + // quote signature + // + + if quote_buf.remaining() != 0 { + let signature_length = quote_buf.get_u32_le(); + if quote_buf.remaining().to_u32() != Some(signature_length) { + return Err(SgxQuoteDecodeError::InvalidSignatureLength(signature_length)); + } + quote.has_signature = true; + } else { + quote.has_signature = false; + } + + Ok(quote) + } + + pub fn is_debug_quote(&self) -> bool { + (self.flags & SGX_FLAGS_DEBUG) != 0 + } + + fn read_zero(buf: &mut impl Buf, pos: usize, count: usize) -> Result<(), SgxQuoteDecodeError> { + for _ in 0..count { + if buf.get_u8() != 0 { + return Err(SgxQuoteDecodeError::NonZeroReserved(pos)); + } + } + Ok(()) + } +} diff --git a/enclave/kbupd_enclave/src/remote_group.rs b/enclave/kbupd_enclave/src/remote_group.rs new file mode 100644 index 0000000..d873158 --- /dev/null +++ b/enclave/kbupd_enclave/src/remote_group.rs @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::prelude::*; + +use std::collections::*; +use std::fmt; +use std::rc::*; + +use rand_core::{RngCore}; +use sgxsd_ffi::{RdRand}; + +use crate::protobufs::kbupd::*; +use crate::protobufs::kbupd_enclave::*; +use crate::protobufs::raft::*; +use crate::remote::*; +use crate::util::*; + +pub trait RemoteGroupPendingRequest { + type RequestId: Clone + Ord + Eq; + type Message: prost::Message; + fn request_id(&self) -> &Self::RequestId; + fn message(&self) -> Rc; + fn min_attestation(&self) -> Option; +} + +pub trait RemoteGroupNode { + fn request_quote(&mut self, request: EnclaveGetQuoteRequest) -> Result<(), ()>; +} + +pub struct RemoteGroupState +where R: RemoteGroupPendingRequest, +{ + name: String, + nodes: Box<[RemoteGroupNodeState]>, + leader: Option, + term: TermId, + pending: BTreeMap>, + + timeout_ticks: u32, + request_quote_ticks: u32, + total_ticks: u32, +} + +pub enum RemoteGroupSendError { + NotYetValid(R), + AlreadySent(R), +} + +struct PendingRequestState { + request: R, + sent_at_tick: u32, +} + +// +// RemoteGroupState impls +// + +struct RemoteGroupNodeState { + remote: T, + last_sent: Option, +} + +impl RemoteGroupState +where T: RemoteMessageSender + 'static, + T: RemoteGroupNode, + R: RemoteGroupPendingRequest + 'static, +{ + pub fn new(name: String, remotes: Vec) -> Self { + let nodes = remotes.into_iter().map(|remote: T| RemoteGroupNodeState { + remote, + last_sent: Default::default(), + }); + Self { + name, + nodes: nodes.collect::>().into(), + leader: Default::default(), + term: Default::default(), + pending: Default::default(), + + timeout_ticks: Default::default(), + request_quote_ticks: Default::default(), + total_ticks: Default::default(), + } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn pending_len(&self) -> usize { + self.pending.len() + } + + pub fn trim_to(&mut self, trim_to_len: usize, ttl_ticks: u32) -> impl DoubleEndedIterator + ExactSizeIterator { + let mut maybe_trim_key: Option<&R::RequestId> = None; + if let Some(trim_count) = self.pending.len().checked_sub(trim_to_len) { + for (pending_request_id, pending_request) in self.pending.iter().take(trim_count) { + if self.total_ticks.checked_sub(pending_request.sent_at_tick) < Some(ttl_ticks) { + break; + } + maybe_trim_key = Some(pending_request_id); + } + } + let trimmed = if let Some(trim_key) = maybe_trim_key { + let split_off = if let Some((new_first_key, _)) = self.pending.range(trim_key..).nth(1) { + let new_first_key = new_first_key.clone(); + self.pending.split_off(&new_first_key) + } else { + BTreeMap::new() + }; + std::mem::replace(&mut self.pending, split_off) + } else { + BTreeMap::new() + }; + + trimmed.into_iter() + .map(|(_, pending_request_state): (_, PendingRequestState)| { + pending_request_state.request + }) + } + + pub fn reset_peer(&mut self, node_id: &NodeId) { + match self.get_node_mut(node_id) { + Some(from_node) => { + from_node.last_sent = Default::default(); + } + None => return, + } + warn!("resetting group {} peer {}", &self.name, node_id); + let maybe_old_leader = + self.leader.and_then(|leader: usize| self.nodes.get(leader)) + .map(|leader: &RemoteGroupNodeState| leader.remote.id()); + + if maybe_old_leader == Some(node_id) { + self.leader = None; + self.choose_leader(); + } + self.flush_requests(); + } + + pub fn contains_authorized_node(&self, node_id: &NodeId) -> bool { + if let Some(node) = self.get_node(node_id) { + node.remote.attestation().is_some() + } else { + false + } + } + + pub fn status(&self) -> Vec { + let mut statuses = Vec::with_capacity(self.nodes.len()); + for (node_idx, node) in self.nodes.iter().enumerate() { + let unsent_requests = if let Some(last_sent) = &node.last_sent { + self.pending.range(last_sent..).count().saturating_sub(1) + } else { + self.pending.len() + }; + let inflight_requests = self.pending.len().saturating_sub(unsent_requests); + statuses.push(EnclavePeerStatus { + node_id: node.remote.id().to_vec(), + attestation: node.remote.attestation(), + replication_status: None, + is_leader: Some(node_idx) == self.leader, + unsent_requests: unsent_requests.to_u64(), + inflight_requests: inflight_requests.to_u64(), + }) + } + statuses + } + + fn get_node_mut(&mut self, node_id: &NodeId) -> Option<&mut RemoteGroupNodeState> { + self.nodes.iter_mut().find_map(|node: &mut RemoteGroupNodeState| { + if node.remote.id() == node_id { + Some(node) + } else { + None + } + }) + } + + fn get_node(&self, node_id: &NodeId) -> Option<&RemoteGroupNodeState> { + self.nodes.iter().find_map(|node: &RemoteGroupNodeState| { + if node.remote.id() == node_id { + Some(node) + } else { + None + } + }) + } + + fn get_leader_node(&self) -> Option<&RemoteGroupNodeState> { + if let Some(leader) = &self.leader { + self.nodes.get(*leader) + } else { + None + } + } + + pub fn timer_tick(&mut self, max_timeout_ticks: u32, max_request_quote_ticks: u32) { + self.timeout_ticks = self.timeout_ticks.saturating_add(1); + self.request_quote_ticks = self.request_quote_ticks.saturating_add(1); + self.total_ticks = self.total_ticks.wrapping_add(1); + if self.timeout_ticks >= max_timeout_ticks { + self.timeout_ticks = Default::default(); + + if !self.pending.is_empty() { + if let Some(leader) = self.get_leader_node() { + info!("group {} timeout on leader {}", &self.name, leader.remote.id()); + self.leader = None; + } + self.choose_leader(); + } + } + if self.request_quote_ticks >= max_request_quote_ticks { + self.request_quote_ticks = Default::default(); + + for node in &mut self.nodes[..] { + let _ignore = node.remote.request_quote(EnclaveGetQuoteRequest {}); + } + } + } + + pub fn remote_authorized(&mut self, node_id: &NodeId) { + if self.get_node(node_id).is_some() { + self.choose_leader(); + } + } + + pub fn remote_not_leader(&mut self, term: TermId, maybe_new_leader: Option<&NodeId>, from_node_id: &NodeId) { + match self.get_node_mut(from_node_id) { + Some(from_node) => { + from_node.last_sent = Default::default(); + } + None => return, + } + let nodes = &self.nodes[..]; + + let maybe_old_leader = + self.leader.and_then(|leader: usize| nodes.get(leader)) + .map(|leader: &RemoteGroupNodeState| leader.remote.id()); + if term >= self.term { + self.term = term; + // prevent re-send storm from a node responding NotLeader while contradictorily asserting itself as leader + if let Some(new_leader) = maybe_new_leader.filter(|new_leader: &&NodeId| new_leader != &from_node_id) { + if Some(new_leader) != maybe_old_leader { + info!("group {} changed leader to {} at term {}", &self.name, new_leader, &self.term.id); + self.leader = nodes.iter().position(|node: &RemoteGroupNodeState| node.remote.id() == new_leader); + } + } else if let Some(old_leader) = maybe_old_leader { + info!("group {} lost leader {} at term {}", &self.name, old_leader, &self.term.id); + self.leader = None; + } + } else if let Some(old_leader) = maybe_old_leader { + if old_leader == from_node_id { + info!("group {} lost leader {} at term {}", &self.name, old_leader, &self.term.id); + self.leader = None; + } + } + self.flush_requests(); + } + + pub fn send(&mut self, request: R) -> Result<(), RemoteGroupSendError> { + let request_id = request.request_id().clone(); + let message = request.message(); + + if Some(&request_id) < self.pending.keys().last() { + return Err(RemoteGroupSendError::AlreadySent(request)); + } + + let nodes = &mut self.nodes[..]; + let maybe_authorized_leader = + self.leader.and_then(|leader: usize| nodes.get_mut(leader)) + .filter(|leader: &&mut RemoteGroupNodeState| leader.remote.attestation().is_some()); + if let btree_map::Entry::Vacant(pending_request_entry) = self.pending.entry(request_id) { + let sent_at_tick = self.total_ticks; + if let Some(authorized_leader) = maybe_authorized_leader { + if request.min_attestation() <= authorized_leader.remote.attestation() { + authorized_leader.send(message); + authorized_leader.mark_sent(Some(request.request_id())); + pending_request_entry.insert(PendingRequestState { request, sent_at_tick }); + Ok(()) + } else { + Err(RemoteGroupSendError::NotYetValid(request)) + } + } else { + pending_request_entry.insert(PendingRequestState { request, sent_at_tick }); + Ok(()) + } + } else { + Err(RemoteGroupSendError::AlreadySent(request)) + } + } + + pub fn handle_reply(&mut self, request_id: &R::RequestId) -> Option { + self.timeout_ticks = Default::default(); + self.pending.remove(request_id) + .map(|request_state: PendingRequestState| request_state.request) + } + + pub fn get_remotes(&self) -> Vec { + self.nodes[..].iter().map(|node: &RemoteGroupNodeState| node.remote.id().clone()).collect() + } + + #[allow(clippy::indexing_slicing, clippy::integer_arithmetic)] + fn choose_leader(&mut self) { + if self.get_leader_node().is_none() { + let mut nodes: Vec = (0..self.nodes.len()).collect(); + for nodes_idx in 0..nodes.len() { + let rand = (RdRand.next_u32().to_usize()) % (self.nodes.len() - nodes_idx); + nodes.swap(nodes_idx, nodes_idx.wrapping_add(rand)); + + let node_idx = nodes[nodes_idx]; + let node = &self.nodes[node_idx]; + if (node.remote.attestation().is_some() && + self.has_unsent_to(node)) + { + self.leader = Some(node_idx); + self.timeout_ticks = Default::default(); + info!("group {} chose random leader {}", &self.name, node.remote.id()); + break; + } + } + } + self.flush_requests(); + } + + fn flush_requests(&mut self) { + let nodes = &mut self.nodes[..]; + let maybe_authorized_leader = + self.leader.and_then(|leader: usize| nodes.get_mut(leader)) + .filter(|leader: &&mut RemoteGroupNodeState| leader.remote.attestation().is_some()); + if let Some(authorized_leader) = maybe_authorized_leader { + let mut queue = Vec::new(); + let mut not_yet_valid_count: u64 = 0; + let mut last_sent_request_id: Option<&R::RequestId> = None; + for pending_request in self.pending.values_mut() { + if !authorized_leader.has_sent(&pending_request.request) { + if pending_request.request.min_attestation() <= authorized_leader.remote.attestation() { + last_sent_request_id = Some(pending_request.request.request_id()); + queue.push(pending_request.request.message()); + } else { + not_yet_valid_count += 1; + } + } + } + if not_yet_valid_count > 0 { + info!("group {} not sending {} messages to new leader {} due to attestation timestamp {}", + &self.name, not_yet_valid_count, authorized_leader.remote.id(), + OptionDisplay(authorized_leader.remote.attestation().as_ref())); + } + if !queue.is_empty() { + info!("group {} resending {} messages to new leader {}", + &self.name, queue.len(), authorized_leader.remote.id()); + for message in queue { + authorized_leader.send(message); + } + } + authorized_leader.mark_sent(last_sent_request_id); + } + } + + fn has_unsent_to(&self, node: &RemoteGroupNodeState) -> bool { + if let Some(last_request) = self.pending.values().last() { + !node.has_sent(&last_request.request) + } else { + true + } + } +} + +impl RemoteGroupNodeState +where T: RemoteMessageSender + 'static, + RequestId: Clone + Ord + Eq, +{ + fn send(&self, request: Rc) { + let _ignore = self.remote.send(request); + } + fn mark_sent(&mut self, sent_request_id: Option<&RequestId>) { + if sent_request_id > self.last_sent.as_ref() { + self.last_sent = sent_request_id.cloned(); + } + } + fn has_sent(&self, request: &R) -> bool + where R: RemoteGroupPendingRequest + 'static, + { + Some(request.request_id()) <= self.last_sent.as_ref() + } +} + +impl fmt::Display for RemoteGroupState +where T: RemoteMessageSender + RemoteGroupNode + 'static, + R: RemoteGroupPendingRequest + 'static, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("RemoteGroupState") + .field("name", &self.name) + .field("nodes", &ListDisplay(self.nodes.iter().map(|node| node.remote.id()))) + .field("leader", &OptionDisplay(self.get_leader_node().map(|node| node.remote.id()))) + .field("term", &DisplayAsDebug(self.term)) + .finish() + } +} + +impl fmt::Display for RemoteGroupSendError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match self { + RemoteGroupSendError::AlreadySent(_) => "AlreadySent", + RemoteGroupSendError::NotYetValid(_) => "NotYetValid", + }; + write!(fmt, "{}", name) + } +} diff --git a/enclave/kbupd_enclave/src/service/frontend.rs b/enclave/kbupd_enclave/src/service/frontend.rs new file mode 100644 index 0000000..7775230 --- /dev/null +++ b/enclave/kbupd_enclave/src/service/frontend.rs @@ -0,0 +1,955 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::prelude::*; + +use std::cmp::{Ordering}; +use std::ops::{Add}; +use std::rc::*; +use std::time::*; + +use hashbrown::{HashMap}; +use prost::{Message}; +use sgx_ffi::util::{SecretValue}; +use sgxsd_ffi::ecalls::{SgxsdMsgFrom}; + +use crate::{kbupd_send}; +use crate::ffi::ecalls::*; +use crate::hasher::{DefaultHasher}; +use crate::protobufs::kbupd::*; +use crate::protobufs::kbupd_client; +use crate::protobufs::kbupd_enclave::*; +use crate::protobufs::raft::*; +use crate::util::*; +use crate::remote::*; +use crate::remote_group::*; +use crate::service::replica::{PartitionKeyRange}; + +const NODE_TYPE: NodeType = NodeType::Frontend; + +// +// data structures +// + +pub struct FrontendState { + config: EnclaveFrontendConfig, + replicas: PeerManager, + partitions: HashMap, + key_ranges: PartitionKeyRanges, + last_request_id: PendingRequestId, +} + +type RemoteReplicaMessageSender = RemoteSender; + +struct Replica { + remote: RemoteState, + group_id: RaftGroupId, +} + +struct Partition { + remote_group: RemoteGroupState, +} + +#[derive(Default)] +struct PartitionKeyRanges { + ranges: Vec<(PartitionKeyRange, RaftGroupId)>, +} + +#[derive(Clone, Default, Eq, PartialEq, Ord, PartialOrd)] +struct PendingRequestId { + id: u64, +} + +#[allow(variant_size_differences)] +enum PendingRequestFrom { + Client(PendingClientRequest), + Untrusted { + untrusted_request_id: u64, + } +} + +struct PendingRequest { + id: PendingRequestId, + message: Rc, + min_attestation: Option, + from: PendingRequestFrom, +} + +pub struct PendingClientRequest { + from: SgxsdMsgFrom, +} + +// +// FrontendState impls +// + +impl FrontendState { + pub fn init(request: StartFrontendRequest) -> Self { + let mut state = Self { + config: request.config, + replicas: PeerManager::new(NODE_TYPE), + partitions: Default::default(), + key_ranges: Default::default(), + last_request_id: Default::default(), + }; + + for partition_config in request.partitions { + state.update_partition(partition_config); + } + + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::StartFrontendReply(StartFrontendReply { + node_id: state.replicas.our_node_id().to_vec(), + })), + }); + + state + } + + fn update_partition(&mut self, config: PartitionConfig) { + let PartitionConfig { group_id, range, node_ids } = config; + let group_id = RaftGroupId { id: group_id }; + if let Some(range) = range { + match PartitionKeyRange::try_from_pb(&range) { + Ok(range) => { + self.key_ranges.update(&group_id, range); + } + Err(()) => { + error!("received partition config for {} with invalid range: {}", &group_id, &range); + return; + } + } + } else { + self.key_ranges.remove(&group_id); + } + if !self.partitions.contains_key(&group_id) { + let mut remotes = Vec::with_capacity(node_ids.len()); + for replica_node_id in node_ids { + if let Some(replica) = self.start_replica_remote(replica_node_id[..].into(), group_id.clone()) { + remotes.push(replica.remote.sender().clone()); + } else { + warn!("couldnt connect to evicted replica {}", NodeId::from(replica_node_id)); + } + } + let group_name = format!("{}", ToHex(&group_id.id)); + self.partitions.insert(group_id, Partition { + remote_group: RemoteGroupState::new(group_name, remotes), + }); + }; + } + + fn start_replica_remote(&mut self, node_id: NodeId, group_id: RaftGroupId) -> Option<&Replica> { + match self.replicas.start_peer(node_id, NodeType::Replica, RemoteAuthorizationType::RemoteOnly) { + Ok(replica_entry) => { + match replica_entry.connect(|remote| Replica { remote, group_id }) { + Ok(replica) => Some(replica), + Err((replica_entry, mapper)) => { + warn!("inserting disconnected replica entry for {} due to connect error", + replica_entry.remote().id()); + Some(replica_entry.insert(mapper)) + } + } + } + Err(Some(replica)) => Some(replica), + Err(None) => None, + } + } + + // + // untrusted messages + // + + pub fn untrusted_message(&mut self, untrusted_message: UntrustedMessage) { + match untrusted_message.inner { + Some(untrusted_message::Inner::StartFrontendRequest(_)) | + Some(untrusted_message::Inner::StartReplicaRequest(_)) => + (), + + Some(untrusted_message::Inner::StartReplicaGroupRequest(_)) => + (), + Some(untrusted_message::Inner::UntrustedTransactionRequest(request)) => + self.handle_untrusted_transaction_request(request), + Some(untrusted_message::Inner::UntrustedXferRequest(_)) => + (), + Some(untrusted_message::Inner::GetEnclaveStatusRequest(request)) => + self.handle_get_enclave_status_request(request), + + Some(untrusted_message::Inner::GetQeInfoReply(reply)) => + self.handle_get_qe_info_reply(reply), + Some(untrusted_message::Inner::GetQuoteReply(reply)) => + self.handle_get_quote_reply(reply), + Some(untrusted_message::Inner::GetAttestationReply(reply)) => + self.handle_get_attestation_reply(reply), + + Some(untrusted_message::Inner::NewMessageSignal(signal)) => + self.handle_new_message_signal(signal), + Some(untrusted_message::Inner::TimerTickSignal(signal)) => + self.handle_timer_tick_signal(signal), + Some(untrusted_message::Inner::SetFrontendConfigSignal(signal)) => + self.handle_set_frontend_config_signal(signal), + Some(untrusted_message::Inner::SetReplicaConfigSignal(_)) => + (), + Some(untrusted_message::Inner::ResetPeerSignal(signal)) => + self.handle_reset_peer_signal(signal), + Some(untrusted_message::Inner::SetVerboseLoggingSignal(signal)) => + self.handle_set_verbose_logging_signal(signal), + + None => (), + } + } + + fn handle_untrusted_transaction_request(&mut self, request: UntrustedTransactionRequest) { + let from = PendingRequestFrom::Untrusted { + untrusted_request_id: request.request_id, + }; + match validate_untrusted_transaction_request(request.data) { + Ok(transaction_request_data) => { + self.request_transaction(transaction_request_data, from); + } + Err(()) => { + error!("received invalid untrusted transaction request"); + self.cancel_pending_request(from); + } + } + } + + fn handle_get_enclave_status_request(&mut self, request: GetEnclaveStatusRequest) { + let memory_status = if request.memory_status { + Some(memory_status()) + } else { + None + }; + let mut partitions = Vec::with_capacity(self.partitions.len()); + for (group_id, partition) in &self.partitions { + partitions.push(EnclaveFrontendPartitionStatus { + group_id: group_id.id.clone(), + nodes: partition.remote_group.status(), + }); + } + let mut ranges = Vec::with_capacity(self.key_ranges.ranges.len()); + for (range, group_id) in &self.key_ranges.ranges { + ranges.push(EnclaveFrontendRangeStatus { + range: range.to_pb(), + group_id: group_id.id.clone(), + }); + } + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::GetEnclaveStatusReply(GetEnclaveStatusReply { + inner: Some(get_enclave_status_reply::Inner::FrontendStatus(EnclaveFrontendStatus { + memory_status, + partitions, + ranges, + })), + })), + }); + } + + fn handle_get_qe_info_reply(&mut self, reply: GetQeInfoReply) { + self.replicas.get_qe_info_reply(reply); + } + + fn handle_get_quote_reply(&mut self, reply: GetQuoteReply) { + self.replicas.get_quote_reply(reply); + } + + fn handle_get_attestation_reply(&mut self, reply: GetAttestationReply) { + if let Some((Replica { remote, .. }, attestation)) = self.replicas.get_attestation_reply(reply) { + let peer_node_id = remote.id().clone(); + self.replica_authorized(attestation, peer_node_id); + } + } + + fn replica_authorized(&mut self, _attestation: AttestationParameters, replica_node_id: NodeId) { + if let Some((_replica, partition)) = Self::get_partition_replica_mut(&mut self.replicas, &mut self.partitions, &replica_node_id) { + partition.remote_group.remote_authorized(&replica_node_id); + } + } + + fn handle_new_message_signal(&mut self, signal: NewMessageSignal) { + match self.replicas.new_message_signal(signal) { + Ok(Some((from, message))) => { + let from_node_id = from.remote_mut().id().clone(); + self.replica_message(message, from_node_id); + } + Ok(None) => (), + Err(peer_entry) => { + warn!("unsolicited connect request from {}: {}", peer_entry.node_id(), peer_entry.connect_request()) + } + } + } + + fn handle_timer_tick_signal(&mut self, _signal: TimerTickSignal) { + self.replicas.timer_tick(self.config.min_connect_timeout_ticks, self.config.max_connect_timeout_ticks); + + for partition in self.partitions.values_mut() { + partition.remote_group.timer_tick(self.config.replica_timeout_ticks, self.config.request_quote_ticks); + } + } + + fn handle_set_frontend_config_signal(&mut self, signal: SetFrontendConfigSignal) { + info!("setting frontend config to {:#}", &signal.config); + self.config = signal.config; + } + + fn handle_reset_peer_signal(&mut self, signal: ResetPeerSignal) { + let node_id: NodeId = signal.peer_node_id.into(); + if let Some((_replica, partition)) = Self::get_partition_replica_mut(&mut self.replicas, &mut self.partitions, &node_id) { + partition.remote_group.reset_peer(&node_id); + } + } + + fn handle_set_verbose_logging_signal(&mut self, signal: SetVerboseLoggingSignal) { + crate::logging::set_verbose_logging_enabled(signal.verbose_logging); + } + + // + // replica messages + // + + fn replica_message(&mut self, replica_message: ReplicaToFrontendMessage, from_node_id: NodeId) { + match replica_message.inner { + Some(replica_to_frontend_message::Inner::TransactionReply(transaction_reply)) => + self.handle_transaction_reply(transaction_reply, from_node_id), + Some(replica_to_frontend_message::Inner::EnclaveGetQuoteReply(reply)) => + self.handle_enclave_get_quote_reply(reply, from_node_id), + None => (), + } + } + + fn handle_transaction_reply(&mut self, transaction_reply: TransactionReply, from_node_id: NodeId) { + if let Some((replica, partition)) = Self::get_partition_replica_mut(&mut self.replicas, &mut self.partitions, &from_node_id) { + match transaction_reply.data { + Some(transaction_reply::Data::ClientResponse(client_reply)) => { + let maybe_pending_request: Option = + partition.remote_group.handle_reply(&PendingRequestId { id: transaction_reply.request_id }); + if let Some(PendingRequestFrom::Client(pending_client_request)) = + maybe_pending_request.map(|pending_request| pending_request.from) + { + pending_client_request.reply(&client_reply); + } else { + info!("pending client request {} not found", transaction_reply.request_id); + } + } + Some(transaction_reply::Data::InvalidRequest(_invalid_request_error)) => { + error!("replica {} reported InvalidRequest {}", &from_node_id, &transaction_reply.request_id); + let maybe_pending_request: Option = + partition.remote_group.handle_reply(&PendingRequestId { id: transaction_reply.request_id }); + if let Some(pending_request) = maybe_pending_request { + self.cancel_pending_request(pending_request.from); + } + } + Some(transaction_reply::Data::InternalError(_internal_error)) => { + warn!("replica {} reported InternalError on request {}!", + &from_node_id, &transaction_reply.request_id); + let maybe_pending_request: Option = + partition.remote_group.handle_reply(&PendingRequestId { id: transaction_reply.request_id }); + if let Some(pending_request) = maybe_pending_request { + self.cancel_pending_request(pending_request.from); + } + } + Some(transaction_reply::Data::CreateBackupReply(create_backup_reply)) => { + let maybe_pending_request: Option = + partition.remote_group.handle_reply(&PendingRequestId { id: transaction_reply.request_id }); + if let Some(PendingRequestFrom::Untrusted { untrusted_request_id }) = + maybe_pending_request.map(|pending_request| pending_request.from) + { + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::UntrustedTransactionReply(UntrustedTransactionReply { + request_id: untrusted_request_id, + data: Some(untrusted_transaction_reply::Data::CreateBackupReply(create_backup_reply)), + })), + }); + } else { + info!("pending untrusted transaction request {} not found", + transaction_reply.request_id); + } + } + Some(transaction_reply::Data::DeleteBackupReply(delete_backup_reply)) => { + let maybe_pending_request: Option = + partition.remote_group.handle_reply(&PendingRequestId { id: transaction_reply.request_id }); + match maybe_pending_request.map(|pending_request| pending_request.from) { + Some(PendingRequestFrom::Client(pending_client_request)) => { + pending_client_request.reply(&kbupd_client::Response { + backup: None, + restore: None, + delete: Some(kbupd_client::DeleteResponse {}), + }); + } + Some(PendingRequestFrom::Untrusted { untrusted_request_id }) => { + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::UntrustedTransactionReply(UntrustedTransactionReply { + request_id: untrusted_request_id, + data: Some(untrusted_transaction_reply::Data::DeleteBackupReply(delete_backup_reply)), + })), + }); + } + None => { + info!("pending client request {} not found", transaction_reply.request_id); + } + } + } + Some(transaction_reply::Data::NotLeader(not_leader_error_data)) => { + let new_leader: Option = not_leader_error_data.leader_node_id.map(NodeId::from); + partition.remote_group.remote_not_leader(not_leader_error_data.term, new_leader.as_ref(), &from_node_id); + verbose!("replica {} reported NotLeader for partition {} with new leader {} at term {}", + replica.remote.id(), &replica.group_id, OptionDisplay(new_leader.as_ref()), + ¬_leader_error_data.term); + } + Some(transaction_reply::Data::WrongPartition(wrong_partition_error_data)) => { + let maybe_pending_request: Option = + partition.remote_group.handle_reply(&PendingRequestId { id: transaction_reply.request_id }); + if let Some(range) = &wrong_partition_error_data.range { + match PartitionKeyRange::try_from_pb(range) { + Ok(range) => { + info!("partition {} reported WrongPartition with range {}", &replica.group_id, range); + self.key_ranges.update(&replica.group_id, range); + } + Err(()) => { + error!("partition {} reported WrongPartition with invalid range {}", + &replica.group_id, range); + } + } + } else { + info!("partition {} reported WrongPartition with no range", &replica.group_id); + self.key_ranges.remove(&replica.group_id); + } + if let Some(new_partition) = wrong_partition_error_data.new_partition { + info!("partition {} reported WrongPartition with new partition {} and range {}", + &replica.group_id, ToHex(&new_partition.group_id), OptionDisplay(new_partition.range.as_ref())); + self.update_partition(new_partition); + } else { + warn!("partition {} reported WrongPartition but didn't know the right one!", &replica.group_id); + } + if let Some(pending_request) = maybe_pending_request { + self.send_transaction_request(pending_request); + } + } + Some(transaction_reply::Data::ServiceIdMismatch(_service_id_mismatch_data)) => { + let maybe_pending_request: Option = + partition.remote_group.handle_reply(&PendingRequestId { id: transaction_reply.request_id }); + if let Some(pending_request) = maybe_pending_request { + warn!("partition {} reported ServiceIdMismatch"); + self.cancel_pending_request(pending_request.from); + } + } + Some(transaction_reply::Data::XferInProgress(_xfer_in_progress_data)) => { + let maybe_pending_request: Option = + partition.remote_group.handle_reply(&PendingRequestId { id: transaction_reply.request_id }); + if let Some(pending_request) = maybe_pending_request { + info!("partition {} reported XferInProgress for backup id {}", + &replica.group_id, OptionDisplay(pending_request.backup_id())); + self.cancel_pending_request(pending_request.from); + } + } + None => { + let maybe_pending_request: Option = + partition.remote_group.handle_reply(&PendingRequestId { id: transaction_reply.request_id }); + if let Some(pending_request) = maybe_pending_request { + self.cancel_pending_request(pending_request.from); + } + } + } + } + } + + fn handle_enclave_get_quote_reply(&mut self, reply: EnclaveGetQuoteReply, from_node_id: NodeId) { + self.replicas.request_attestation(reply.sgx_quote, from_node_id); + } + + // + // client requests + // + + pub fn client_request(&mut self, data: transaction_request::Data, from: SgxsdMsgFrom) { + self.request_transaction(data, PendingRequestFrom::Client(PendingClientRequest { from })); + } + + fn request_transaction(&mut self, data: transaction_request::Data, from: PendingRequestFrom) { + let id = self.last_request_id.clone() + 1; + self.last_request_id = id.clone(); + let min_attestation = match &data { + transaction_request::Data::Create(_) | + transaction_request::Data::Delete(_) => { + None + } + transaction_request::Data::Backup(BackupTransactionRequest { valid_from, .. }) | + transaction_request::Data::Restore(RestoreTransactionRequest { valid_from, .. }) => { + Some(AttestationParameters::new(Duration::from_secs(*valid_from))) + } + }; + let message = Rc::new(FrontendToReplicaMessage { + inner: Some(frontend_to_replica_message::Inner::TransactionRequest(TransactionRequest { + request_id: id.id, + data: Some(data), + })), + }); + let pending_request = PendingRequest { + id, + message, + min_attestation, + from, + }; + self.send_transaction_request(pending_request); + } + + fn send_transaction_request(&mut self, mut pending_request: PendingRequest) { + let Self { partitions, .. } = self; + + if pending_request.id != self.last_request_id { + pending_request.id = self.last_request_id.clone() + 1; + self.last_request_id = pending_request.id.clone(); + match &mut Rc::make_mut(&mut pending_request.message).inner { + Some(frontend_to_replica_message::Inner::TransactionRequest(txn_request)) => { + txn_request.request_id = pending_request.id.id; + } + Some(frontend_to_replica_message::Inner::EnclaveGetQuoteRequest(_)) | + None => (), + } + } + + if let Some(backup_id) = pending_request.backup_id() { + let maybe_group_id: Option<&RaftGroupId> = self.key_ranges.find(backup_id); + let maybe_partition: Option<&mut Partition> = maybe_group_id.and_then(|group_id| partitions.get_mut(group_id)); + if let Some(partition) = maybe_partition { + let trimmed = partition.remote_group.trim_to(self.config.pending_request_count.saturating_sub(1).to_usize(), + self.config.pending_request_ttl); + if trimmed.len() != 0 { + info!("dropping {} old pending requests for partition {}", trimmed.len(), partition.remote_group.name()); + } + + if partition.remote_group.pending_len() < self.config.pending_request_count.to_usize() { + match partition.remote_group.send(pending_request) { + Ok(()) => (), + Err(RemoteGroupSendError::NotYetValid(pending_request)) => { + reject_pending_request_not_yet_valid(pending_request); + } + Err(RemoteGroupSendError::AlreadySent(pending_request)) => { + warn!("dropping already sent request {}", &pending_request.id.id); + self.cancel_pending_request(pending_request.from); + } + } + } else { + self.cancel_pending_request(pending_request.from); + } + + for trimmed_pending_request in trimmed { + self.cancel_pending_request(trimmed_pending_request.from) + } + } else { + warn!("dropping request for missing partition of {}", backup_id); + self.cancel_pending_request(pending_request.from); + } + } else { + self.cancel_pending_request(pending_request.from); + } + } + + fn cancel_pending_request(&mut self, from: PendingRequestFrom) { + match from { + PendingRequestFrom::Untrusted { untrusted_request_id } => { + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::UntrustedTransactionReply(UntrustedTransactionReply { + request_id: untrusted_request_id, + data: None, + })), + }); + } + PendingRequestFrom::Client(pending_client_request) => { + drop(pending_client_request); + } + } + } + + fn get_partition_replica_mut<'a, 'b>(replicas: &'a mut PeerManager, + partitions: &'b mut HashMap, + node_id: &NodeId) + -> Option<(&'a mut Replica, &'b mut Partition)> { + let replica = replicas.get_peer_mut(node_id)?; + let partition = partitions.get_mut(&replica.group_id)?; + Some((replica, partition)) + } + + pub fn decode_request(request_type: u32, backup_id: Vec, request_data: &[u8]) -> Result { + let request = kbupd_client::Request::decode(request_data).map_err(|_| ())?; + let backup_id = BackupId::try_from_slice(&backup_id)?; + match request { + kbupd_client::Request { + backup: Some(backup_request), + restore: None, + delete: None, + } => { + match request_type { + KBUPD_REQUEST_TYPE_ANY | + KBUPD_REQUEST_TYPE_BACKUP => { + Self::validate_backup_request(backup_id, backup_request) + } + _ => Err(()), + } + } + kbupd_client::Request { + backup: None, + restore: Some(restore_request), + delete: None, + } => { + match request_type { + KBUPD_REQUEST_TYPE_ANY | + KBUPD_REQUEST_TYPE_RESTORE => { + Self::validate_restore_request(backup_id, restore_request) + } + _ => Err(()), + } + } + kbupd_client::Request { + backup: None, + restore: None, + delete: Some(delete_request), + } => { + match request_type { + KBUPD_REQUEST_TYPE_ANY | + KBUPD_REQUEST_TYPE_DELETE => { + Self::validate_delete_request(backup_id, delete_request) + } + _ => Err(()), + } + } + _ => Err(()), + } + } + + fn validate_backup_request(backup_id: BackupId, mut request: kbupd_client::BackupRequest) -> Result { + if let kbupd_client::BackupRequest { + service_id, + backup_id: Some(request_backup_id), + nonce: Some(nonce), + valid_from: Some(valid_from), + data: Some(data), + pin: Some(pin), + tries: Some(tries), + } = &mut request { + if (Self::validate_request_service_id(service_id) && + request_backup_id == &backup_id.id && + nonce.len() == 32 && + data.len() == 32 && + pin.len() == 32 && + *tries != 0 && *tries <= u16::max_value().into()) + { + Ok(transaction_request::Data::Backup(BackupTransactionRequest { + service_id: service_id.take(), + backup_id, + nonce: std::mem::replace(nonce, Vec::new()), + valid_from: *valid_from, + data: SecretBytes { data: std::mem::replace(data, Vec::new()) }, + pin: SecretBytes { data: std::mem::replace(pin, Vec::new()) }, + tries: *tries, + })) + } else { + Err(()) + } + } else { + Err(()) + } + } + + fn validate_restore_request(backup_id: BackupId, mut request: kbupd_client::RestoreRequest) -> Result { + if let kbupd_client::RestoreRequest { + service_id, + backup_id: Some(request_backup_id), + nonce: Some(nonce), + valid_from: Some(valid_from), + pin: Some(pin), + } = &mut request { + if (Self::validate_request_service_id(service_id) && + request_backup_id == &backup_id.id && + nonce.len() == 32 && + pin.len() == 32) + { + Ok(transaction_request::Data::Restore(RestoreTransactionRequest { + service_id: service_id.take(), + backup_id, + valid_from: *valid_from, + nonce: std::mem::replace(nonce, Vec::new()), + pin: SecretBytes { data: std::mem::replace(pin, Vec::new()), }, + })) + } else { + Err(()) + } + } else { + Err(()) + } + } + + fn validate_delete_request(backup_id: BackupId, request: kbupd_client::DeleteRequest) -> Result { + if let kbupd_client::DeleteRequest { + service_id, + backup_id: Some(request_backup_id), + } = request { + if (Self::validate_request_service_id(&service_id) && + request_backup_id == backup_id.id) + { + Ok(transaction_request::Data::Delete(DeleteTransactionRequest { + service_id, + backup_id, + })) + } else { + Err(()) + } + } else { + Err(()) + } + } + + fn validate_request_service_id(maybe_service_id: &Option>) -> bool { + if let Some(service_id) = maybe_service_id { + service_id.len() == 32 + } else { + true + } + } +} + +fn reject_pending_request_not_yet_valid(pending_request: PendingRequest) { + match pending_request.from { + PendingRequestFrom::Client(pending_client_request) => { + info!("rejecting not yet valid client request {} requiring {}", + &pending_request.id.id, OptionDisplay(pending_request.min_attestation.as_ref())); + match &pending_request.message.as_ref().inner { + Some(frontend_to_replica_message::Inner::TransactionRequest(TransactionRequest { data, .. })) => { + match data { + Some(transaction_request::Data::Backup(_)) => { + pending_client_request.reply(&kbupd_client::Response { + backup: Some(kbupd_client::BackupResponse { + status: Some(kbupd_client::backup_response::Status::NotYetValid.into()), + nonce: None, + }), + restore: None, + delete: None, + }); + } + Some(transaction_request::Data::Restore(_)) => { + pending_client_request.reply(&kbupd_client::Response { + backup: None, + restore: Some(kbupd_client::RestoreResponse { + status: Some(kbupd_client::restore_response::Status::NotYetValid.into()), + nonce: None, + data: None, + tries: None, + }), + delete: None, + }); + } + Some(transaction_request::Data::Create(_)) | + Some(transaction_request::Data::Delete(_)) | + None => (), + } + } + Some(frontend_to_replica_message::Inner::EnclaveGetQuoteRequest(_)) | + None => (), + } + } + PendingRequestFrom::Untrusted { untrusted_request_id } => { + info!("rejecting not yet valid untrusted request {} requiring {}", + untrusted_request_id, OptionDisplay(pending_request.min_attestation.as_ref())); + } + } +} + +// +// PartitionKeyRanges impls +// + +impl PartitionKeyRanges { + fn range_cmp(one: &PartitionKeyRange, two: &PartitionKeyRange) -> Ordering { + (one.first(), one.last()).cmp(&(two.first(), two.last())) + } + fn entry_cmp(one: &(PartitionKeyRange, RaftGroupId), two: &(PartitionKeyRange, RaftGroupId)) -> Ordering { + Self::range_cmp(&one.0, &two.0) + } + fn key_cmp(range: &PartitionKeyRange, key: &BackupId) -> Ordering { + match range.first().as_ref().cmp(&key.id) { + Ordering::Greater => Ordering::Greater, + Ordering::Less | + Ordering::Equal => { + match range.last().as_ref().cmp(&key.id) { + Ordering::Less => Ordering::Less, + Ordering::Greater | + Ordering::Equal => Ordering::Equal, + } + } + } + } + fn update(&mut self, update_group_id: &RaftGroupId, update_range: PartitionKeyRange) { + let mut matches = + self.ranges.iter_mut() + .filter(|(_, group_id)| group_id == update_group_id) + .peekable(); + if matches.peek().is_none() { + match self.ranges[..].binary_search_by(|(range, _)| Self::range_cmp(range, &update_range)) + .and_then(|ranges_index| self.ranges.get_mut(ranges_index).ok_or(ranges_index)) + { + Ok((_, group_id)) => { + *group_id = update_group_id.clone(); + } + Err(ranges_index) => { + self.ranges.insert(ranges_index, (update_range, update_group_id.clone())); + } + } + } else { + for (range, _) in matches { + *range = update_range; + } + } + self.ranges[..].sort_unstable_by(Self::entry_cmp); + } + fn remove(&mut self, remove_group_id: &RaftGroupId) { + self.ranges.retain(|(_range, group_id)| group_id != remove_group_id); + } + fn find<'a>(&'a self, key: &BackupId) -> Option<&'a RaftGroupId> { + self.ranges[..] + .binary_search_by(|(range, _)| Self::key_cmp(range, key)) + .ok() + .and_then(|ranges_index| self.ranges.get(ranges_index)) + .filter(|(range, _group_id)| range.contains_id(key)) + .map(|(_range, group_id)| group_id) + } +} + +// +// PendingRequestId impls +// + +impl Add for PendingRequestId { + type Output = Self; + fn add(self, inc: u64) -> Self { + Self { id: self.id.checked_add(inc).unwrap_or_else(|| panic!("overflow")) } + } +} + +// +// PendingRequest impls +// + +impl PendingRequest { + fn backup_id(&self) -> Option<&BackupId> { + if let Some(frontend_to_replica_message::Inner::TransactionRequest(TransactionRequest { data: Some(data), .. })) = &self.message.inner { + match data { + transaction_request::Data::Create(create_request) => Some(&create_request.backup_id), + transaction_request::Data::Backup(backup_request) => Some(&backup_request.backup_id), + transaction_request::Data::Restore(restore_request) => Some(&restore_request.backup_id), + transaction_request::Data::Delete(delete_request) => Some(&delete_request.backup_id), + } + } else { + None + } + } +} + +impl RemoteGroupPendingRequest for PendingRequest { + type RequestId = PendingRequestId; + type Message = FrontendToReplicaMessage; + fn request_id(&self) -> &Self::RequestId { + &self.id + } + fn message(&self) -> Rc { + Rc::clone(&self.message) + } + fn min_attestation(&self) -> Option { + self.min_attestation + } +} + +// +// PendingClientRequest impls +// + +impl PendingClientRequest { + pub fn reply(self, reply: &kbupd_client::Response) { + let mut data = SecretValue::new(Vec::with_capacity(reply.encoded_len())); + match reply.encode(data.get_mut()) { + Ok(()) => (), + Err(_) => { + error!("error encoding client reply"); + return; + } + } + match self.from.reply(data.get_mut()) { + Ok(()) => { + // no need to erase, as SgxsdFrom::reply encrypts in-place + data.get_mut().clear(); + } + Err(sgx_status) => { + error!("error replying to client request: {}", sgx_status); + } + } + } +} + +// +// Replica impls +// + +impl Peer for Replica { + type Message = ReplicaToFrontendMessage; + fn remote_mut(&mut self) -> &mut dyn Remote { + &mut self.remote + } + fn recv(&mut self, msg_data: &[u8]) -> Result { + self.remote.recv(msg_data) + } + fn send_quote_reply(&mut self, _reply: EnclaveGetQuoteReply) -> Result<(), ()> { + Ok(()) + } +} + +// +// RemoteSender impls +// + +impl RemoteGroupNode for RemoteSender { + fn request_quote(&mut self, request: EnclaveGetQuoteRequest) -> Result<(), ()> { + self.send(Rc::new(FrontendToReplicaMessage { + inner: Some(frontend_to_replica_message::Inner::EnclaveGetQuoteRequest(request)), + })) + } +} + +// +// internal +// + +fn validate_untrusted_transaction_request(request_data: Option) + -> Result { + match request_data { + Some(untrusted_transaction_request::Data::CreateBackupRequest(create_backup_request)) => { + if create_backup_request.backup_id.id.len() == 32 { + Ok(transaction_request::Data::Create(create_backup_request)) + } else { + Err(()) + } + } + Some(untrusted_transaction_request::Data::DeleteBackupRequest(delete_backup_request)) => { + if delete_backup_request.backup_id.id.len() == 32 { + Ok(transaction_request::Data::Delete(DeleteTransactionRequest { + service_id: None, + backup_id: delete_backup_request.backup_id, + })) + } else { + Err(()) + } + } + None => Err(()), + } +} diff --git a/enclave/kbupd_enclave/src/service/main.rs b/enclave/kbupd_enclave/src/service/main.rs new file mode 100644 index 0000000..9e85744 --- /dev/null +++ b/enclave/kbupd_enclave/src/service/main.rs @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::cell::*; + +use sgx_ffi::sgx::*; +use sgxsd_ffi::ecalls::*; + +use crate::ffi::ecalls::*; +use crate::protobufs::kbupd::*; +use crate::protobufs::kbupd::untrusted_message; +use crate::service::frontend::*; +use crate::service::replica::*; + +// +// public api +// + +#[allow(variant_size_differences)] +pub enum ServiceState { + NotStarted, + Frontend(FrontendState), + Replica(ReplicaState), +} + +pub struct SgxsdState { +} + +#[cfg(not(any(test, feature = "test")))] +pub fn whereis(fun: F) -> R +where F: FnOnce(&RefCell) -> R +{ + #[thread_local] + static SERVICE: RefCell = RefCell::new(ServiceState::NotStarted); + + fun(&SERVICE) +} + +#[cfg(any(test, feature = "test"))] +pub fn whereis(fun: F) -> R +where F: FnOnce(&RefCell) -> R +{ + thread_local! { + static SERVICE: RefCell = RefCell::new(ServiceState::NotStarted); + } + + SERVICE.with(fun) +} + +// +// ServiceState impls +// + +impl KbupdService for ServiceState { + fn untrusted_message(&mut self, msg: UntrustedMessage) { + match msg.inner { + Some(untrusted_message::Inner::StartFrontendRequest(start_frontend_req)) => { + if let ServiceState::NotStarted = self { + *self = ServiceState::Frontend(FrontendState::init(start_frontend_req)); + } else { + warn!("node service already started"); + } + } + Some(untrusted_message::Inner::StartReplicaRequest(start_replica_req)) => { + if let ServiceState::NotStarted = self { + *self = ServiceState::Replica(ReplicaState::init(start_replica_req)); + } else { + warn!("node service already started"); + } + } + Some(_) => { + match self { + ServiceState::Replica(replica) => { + replica.untrusted_message(msg); + } + ServiceState::Frontend(frontend) => { + frontend.untrusted_message(msg); + } + ServiceState::NotStarted => { + warn!("node service not started"); + } + } + } + None => { + } + } + } +} + +impl SgxsdServer for SgxsdState { + type InitArgs = StartArgs; + type HandleCallArgs = CallArgs; + type TerminateArgs = StopArgs; + + fn init(_args: Option<&Self::InitArgs>) -> Result { + Ok(Self {}) + } + + fn handle_call(&mut self, + args: Option<&Self::HandleCallArgs>, + request_data: &[u8], + from: SgxsdMsgFrom) + -> Result<(), (SgxStatus, SgxsdMsgFrom)> + { + let args = match args { + Some(args) => args, + None => return Err((SGX_ERROR_INVALID_PARAMETER, from)), + }; + match FrontendState::decode_request(args.request_type, args.backup_id.to_vec(), request_data) { + Ok(request) => { + whereis(|service_ref| { + let mut service = service_ref.borrow_mut(); + if let ServiceState::Frontend(frontend) = &mut *service { + frontend.client_request(request, from); + Ok(()) + } else { + warn!("frontend service not started"); + Err((SGX_ERROR_INVALID_STATE, from)) + } + }) + } + Err(()) => { + Err((SGX_ERROR_INVALID_PARAMETER, from)) + } + } + } + + fn terminate(self, _args: Option<&Self::TerminateArgs>) -> Result<(), SgxStatus> { + Ok(()) + } +} diff --git a/enclave/kbupd_enclave/src/service/mod.rs b/enclave/kbupd_enclave/src/service/mod.rs new file mode 100644 index 0000000..d9caab0 --- /dev/null +++ b/enclave/kbupd_enclave/src/service/mod.rs @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod main; +pub mod frontend; +pub mod replica; diff --git a/enclave/kbupd_enclave/src/service/replica/mod.rs b/enclave/kbupd_enclave/src/service/replica/mod.rs new file mode 100644 index 0000000..766427c --- /dev/null +++ b/enclave/kbupd_enclave/src/service/replica/mod.rs @@ -0,0 +1,1472 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +mod partition_data; +mod partition_key_range; +mod replica_group; + +use crate::prelude::*; + +use std::convert::{TryInto}; +use std::collections::*; +use std::time::*; +use std::rc::*; + +use prost::{Message}; +use sgx_ffi::util::{SecretValue}; +use sgxsd_ffi::{RdRand}; + +use crate::ffi::ecalls::{kbupd_send}; +use crate::lru::*; +use crate::protobufs::kbupd::*; +use crate::protobufs::kbupd_enclave::*; +use crate::protobufs::kbupd_client; +use crate::protobufs::raft::*; +use crate::storage::*; +use crate::raft::*; +use crate::remote::*; +use crate::remote_group::*; +use crate::util; +use crate::util::*; + +use self::partition_data::*; +use self::replica_group::*; + +pub use self::partition_key_range::{PartitionKey, PartitionKeyRange}; + +// +// data structures +// + +pub struct ReplicaState { + config: EnclaveReplicaConfig, + peers: PeerManager, + frontends: Lru, + partition: Option, +} + +struct Partition { + group: ReplicaGroupState, + data: PartitionData, + + create_group_request: CreateRaftGroupRequest, +} + +type ReplicaRemoteSender = RemoteSender; + +enum PeerState { + Frontend { + remote: RemoteState, + lru_entry: Weak>, + }, + Replica { + remote: RemoteState, + authorized: bool, + } +} + +enum PeerMessage { + Frontend(FrontendToReplicaMessage), + Replica(ReplicaToReplicaMessage), +} + +// +// ReplicaState impls +// + +impl ReplicaState { + pub fn init(request: StartReplicaRequest) -> Self { + let state = Self { + peers: PeerManager::new(NodeType::Replica), + config: request.config, + frontends: Lru::new(), + partition: None, + }; + + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::StartReplicaReply(StartReplicaReply { + node_id: state.node_id().to_vec(), + })), + }); + + state + } + + pub fn node_id(&self) -> &NodeId { + self.peers.our_node_id() + } + + // + // untrusted messages + // + + pub fn untrusted_message(&mut self, untrusted_message: UntrustedMessage) { + match untrusted_message.inner { + Some(untrusted_message::Inner::StartFrontendRequest(_)) | + Some(untrusted_message::Inner::StartReplicaRequest(_)) => + (), + + Some(untrusted_message::Inner::StartReplicaGroupRequest(request)) => + self.handle_start_replica_group_request(request), + Some(untrusted_message::Inner::UntrustedTransactionRequest(request)) => + warn!("received untrusted transaction request: {}", request), + Some(untrusted_message::Inner::UntrustedXferRequest(request)) => + self.handle_untrusted_xfer_request(request), + Some(untrusted_message::Inner::GetEnclaveStatusRequest(request)) => + self.handle_get_enclave_status_request(request), + + Some(untrusted_message::Inner::GetQeInfoReply(reply)) => + self.handle_get_qe_info_reply(reply), + Some(untrusted_message::Inner::GetQuoteReply(reply)) => + self.handle_get_quote_reply(reply), + Some(untrusted_message::Inner::GetAttestationReply(reply)) => + self.handle_get_attestation_reply(reply), + + Some(untrusted_message::Inner::NewMessageSignal(signal)) => + self.handle_new_message_signal(signal), + Some(untrusted_message::Inner::TimerTickSignal(signal)) => + self.handle_timer_tick_signal(signal), + Some(untrusted_message::Inner::SetFrontendConfigSignal(_)) => + (), + Some(untrusted_message::Inner::SetReplicaConfigSignal(signal)) => + self.handle_set_replica_config_signal(signal), + Some(untrusted_message::Inner::ResetPeerSignal(signal)) => + self.handle_reset_peer_signal(signal), + Some(untrusted_message::Inner::SetVerboseLoggingSignal(signal)) => + self.handle_set_verbose_logging_signal(signal), + + None => (), + } + } + + fn handle_start_replica_group_request(&mut self, request: StartReplicaGroupRequest) { + let group_id = generate_group_id(); + let service_id = if request.source_partition.is_some() { + None + } else { + Some(generate_service_id()) + }; + + let mut node_ids = request.peer_node_ids; + if !node_ids.iter().any(|peer| peer[..] == self.node_id()[..]) { + node_ids.push(self.node_id().to_vec()); + } + + let create_group_request = CreateRaftGroupRequest { + service_id, + group_id, + node_ids, + config: request.config, + source_partition: request.source_partition, + }; + let _ignore = self.create_raft_group(create_group_request); + + if let Some(partition) = &mut self.partition { + let _raft_msg = partition.group.raft.timeout(); + } + + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::StartReplicaGroupReply(StartReplicaGroupReply { + service_id: self.partition.as_ref().and_then(|partition| partition.data.service_id().cloned()), + group_id: self.partition.as_ref().map(|partition| partition.group.id().id.clone()), + })), + }); + } + + fn handle_untrusted_xfer_request(&mut self, untrusted_xfer_request: UntrustedXferRequest) { + let command = match untrusted_xfer_request.data { + Some(untrusted_xfer_request::Data::XferControlCommand(command)) => command, + None => { + warn!("No XferControlCommand"); + return; + } + }; + + let request_id = untrusted_xfer_request.request_id; + + let maybe_status = match XferControlCommand::from_i32(command) { + Some(XferControlCommand::Start) => { + let status = self.start_partitioning().err().unwrap_or(UntrustedXferReplyStatus::Ok); + Some(status) + } + Some(XferControlCommand::Pause) => { + info!("requesting pause of partitioning process"); + let pause_xfer_txn = TransactionData { + inner: Some(transaction_data::Inner::PauseXfer(PauseXferTransaction { + request_id, + })), + }; + self.request_transaction(pause_xfer_txn); + None + } + Some(XferControlCommand::Resume) => { + self.resume_partitioning(request_id).err() + } + Some(XferControlCommand::Finish) => { + info!("requesting finish of partitioning process"); + let finish_xfer_txn = TransactionData { + inner: Some(transaction_data::Inner::FinishXfer(FinishXferTransaction { + request_id, + force: false, + })), + }; + self.request_transaction(finish_xfer_txn); + None + } + Some(XferControlCommand::Cancel) => { + warn!("requesting cancel of partitioning process"); + let finish_xfer_txn = TransactionData { + inner: Some(transaction_data::Inner::FinishXfer(FinishXferTransaction { + request_id, + force: true, + })), + }; + self.request_transaction(finish_xfer_txn); + None + } + None => { + warn!("Unknown XferControlCommand {}", command); + Some(UntrustedXferReplyStatus::Unknown) + } + }; + + if let Some(status) = maybe_status { + send_untrusted_xfer_reply(request_id, status); + } + } + + fn start_partitioning(&mut self) -> Result<(), UntrustedXferReplyStatus> { + let partition = match &mut self.partition { + Some(partition) => partition, + None => { + warn!("Tried to start partitioning without a partition!"); + return Err(UntrustedXferReplyStatus::InvalidState); + } + }; + let xfer_source = match partition.data.xfer_state_mut() { + XferState::DestinationPartition(xfer_source) => xfer_source, + _ => { + warn!("Tried to start partitioning as a non-destination replica!"); + return Err(UntrustedXferReplyStatus::InvalidState); + } + } +; + let node_ids: Vec<_> = + (partition.group.raft.peers().iter()) + .map(|node_id| node_id.to_vec()) + .chain(std::iter::once(self.peers.our_node_id().to_vec())) + .collect(); + + let chunk_size = self.config.transfer_chunk_size; + info!("requesting xfer of range {} chunk size {}", xfer_source.desired_range(), chunk_size); + + let request = PendingXferRequest { + id: PendingXferRequestId::XferRequest, + message: Rc::new(ReplicaToReplicaMessage { + inner: Some(replica_to_replica_message::Inner::XferRequest(XferRequest { + group_id: partition.group.id().clone(), + chunk_size, + full_range: xfer_source.desired_range().to_pb(), + node_ids, + })), + }), + min_attestation: None, + }; + if let Err(error) = xfer_source.remote_group_mut().send(request) { + error!("error sending XferRequest: {}", &error); + return Err(UntrustedXferReplyStatus::InvalidState); + } + Ok(()) + } + + fn resume_partitioning(&mut self, request_id: u64) -> Result<(), UntrustedXferReplyStatus> { + let partition = match &mut self.partition { + Some(partition) => partition, + None => { + warn!("Tried to resume partitioning without a partition!"); + return Err(UntrustedXferReplyStatus::InvalidState); + } + }; + let xfer_destination = match partition.data.xfer_state() { + XferState::SourcePartition(xfer_destination) => xfer_destination, + _ => { + warn!("Tried to resume partitioning as a non-source replica!"); + return Err(UntrustedXferReplyStatus::InvalidState); + } + }; + let chunk_size = self.config.replication_chunk_size.min(xfer_destination.chunk_size()); + let chunk_last = partition.data.next_chunk_last(chunk_size, xfer_destination.full_range()); + info!("requesting resume of partitioning process with next chunk {}", &chunk_last); + let resume_xfer_txn = TransactionData { + inner: Some(transaction_data::Inner::ResumeXfer(ResumeXferTransaction { + request_id, + chunk_last, + })), + }; + self.request_transaction(resume_xfer_txn); + Ok(()) + } + + fn handle_get_enclave_status_request(&mut self, request: GetEnclaveStatusRequest) { + let partition = if let Some(partition) = &self.partition { + let mut peers = Vec::new(); + for node_id in partition.group.raft.peers() { + let attestation = partition.group.get(node_id).and_then(|peer| peer.attestation()); + let replication_status = partition.group.raft.replication_state(node_id).map(|replication: &ReplicationState| { + EnclavePeerReplicationStatus { + next_index: replication.next_idx.id, + match_index: replication.match_idx.id, + inflight_index: replication.inflight.map(|inflight_log_idx: LogIdx| inflight_log_idx.id), + probing: replication.send_probe, + } + }); + peers.push(EnclavePeerStatus { + node_id: node_id.to_vec(), + attestation, + replication_status, + is_leader: partition.group.raft.leader().0 == Some(node_id), + unsent_requests: Default::default(), + inflight_requests: Default::default(), + }); + } + Some(EnclaveReplicaPartitionStatus { + group_id: partition.group.id().id.clone(), + service_id: partition.data.service_id().map(|service_id| service_id.id.clone()), + range: partition.data.range().map(PartitionKeyRange::to_pb), + peers, + min_attestation: partition.group.attestation(), + is_leader: partition.group.raft.is_leader(), + current_term: partition.group.raft.leader().1.id, + prev_log_index: partition.group.raft.log().prev_idx().id, + last_applied_index: partition.group.raft.last_applied().id, + commit_index: partition.group.raft.commit_idx().id, + last_log_index: partition.group.raft.log().last_idx().id, + last_log_term: partition.group.raft.log().last_term().id, + log_data_length: partition.group.raft.log().data_len().to_u64(), + backup_count: partition.data.storage_len().to_u64(), + xfer_status: partition.data.xfer_status(), + }) + } else { + None + }; + let memory_status = if request.memory_status { + Some(memory_status()) + } else { + None + }; + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::GetEnclaveStatusReply(GetEnclaveStatusReply { + inner: Some(get_enclave_status_reply::Inner::ReplicaStatus(EnclaveReplicaStatus { + memory_status, + partition, + })), + })), + }); + } + + fn handle_get_qe_info_reply(&mut self, reply: GetQeInfoReply) { + self.peers.get_qe_info_reply(reply); + } + + fn handle_get_quote_reply(&mut self, reply: GetQuoteReply) { + self.peers.get_quote_reply(reply); + } + + fn handle_get_attestation_reply(&mut self, reply: GetAttestationReply) { + match self.peers.get_attestation_reply(reply) { + Some((peer @ PeerState::Replica { .. }, attestation)) => { + let peer_node_id = peer.remote_mut().id().clone(); + let _ignore = peer.authorize(); + self.replica_authorized(attestation, peer_node_id); + } + Some((PeerState::Frontend { .. }, _attestation)) => { + // we don't actually care about frontend authorization being refreshed + } + None => (), + } + } + + fn replica_authorized(&mut self, attestation: AttestationParameters, node_id: NodeId) { + info!("authorized peer {} with {}", &node_id, &attestation); + if let Some(partition) = &mut self.partition { + if let Some(remote_group) = partition.data.xfer_state_mut().remote_group_mut() { + remote_group.remote_authorized(&node_id); + } + if let Some(replica) = partition.group.get(&node_id) { + if partition.group.raft.peers().contains(&node_id) { + let create_raft_group_req = Rc::new(ReplicaToReplicaMessage { + inner: Some(replica_to_replica_message::Inner::CreateRaftGroupRequest(partition.create_group_request.clone())), + }); + match replica.sender.send(create_raft_group_req) { + Ok(()) => (), + Err(()) => { + error!("error sending raft group to {}", &node_id); + } + } + + if let Some(raft_message) = partition.group.raft.reset_peer(node_id) { + partition.group.send_raft_message(raft_message); + } + + self.raft_step(); + } + } + } + } + + fn handle_timer_tick_signal(&mut self, signal: TimerTickSignal) { + let now = Duration::from_secs(signal.now_secs); + + self.peers.timer_tick(self.config.min_connect_timeout_ticks, self.config.max_connect_timeout_ticks); + + let remote_group_timeout_ticks = self.config.election_timeout_ticks.saturating_mul(2); + + if let Some(partition) = &mut self.partition { + if let Some(remote_group) = partition.data.xfer_state_mut().remote_group_mut() { + remote_group.timer_tick(remote_group_timeout_ticks, self.config.request_quote_ticks); + } + + if let Some(txn_data) = partition.group.timer_tick(self.config.attestation_expiry_ticks, self.config.request_quote_ticks, now) { + let mut encoded_txn_data = Vec::with_capacity(txn_data.encoded_len()); + if let Ok(()) = txn_data.encode(&mut encoded_txn_data) { + let _ignore = partition.group.raft.client_request(encoded_txn_data); + } else { + error!("error encoding timer tick transaction"); + } + } + + if let Some(raft_message) = partition.group.raft.timer_tick() { + partition.group.send_raft_message(raft_message); + } + self.raft_step(); + } + } + + fn handle_new_message_signal(&mut self, new_message_signal: NewMessageSignal) { + match self.peers.new_message_signal(new_message_signal) { + Ok(Some((from, PeerMessage::Frontend(message)))) => { + let from_node_id = from.remote_mut().id().clone(); + if let PeerState::Frontend { lru_entry, .. } = from { + self.frontends.bump(lru_entry); + } + self.frontend_message(message, from_node_id); + } + Ok(Some((from, PeerMessage::Replica(message)))) => { + let from_node_id = from.remote_mut().id().clone(); + if let Some(attestation) = from.authorize() { + self.replica_authorized(attestation, from_node_id.clone()); + } + self.replica_message(message, from_node_id); + } + Ok(None) => (), + Err(peer_entry) => { + match NodeType::from_i32(peer_entry.connect_request().node_type) { + Some(NodeType::Frontend) => { + info!("accepted frontend connection from {}", &peer_entry.node_id()); + let frontends = &mut self.frontends; + let _ignore = peer_entry.accept(|remote| { + PeerState::Frontend { + lru_entry: frontends.push_back(remote.id().clone()), + remote, + } + }, RemoteAuthorizationType::SelfOnly); + if self.frontends.len() > self.config.max_frontend_count.to_usize() { + self.pop_frontend(); + } + } + Some(NodeType::Replica) => { + info!("accepting replica connection from {}", peer_entry.node_id()); + let _ignore = peer_entry.accept(PeerState::new_replica, RemoteAuthorizationType::Mutual); + } + None | Some(NodeType::None) => { + warn!("bad node type in connect request from {}: {}", + peer_entry.node_id(), peer_entry.connect_request().node_type); + } + } + } + } + } + + fn pop_frontend(&mut self) { + if let Some(lru_entry) = self.frontends.pop_front() { + self.peers.remove_peer(lru_entry.get()); + info!("evicted connection state for old frontend {}", lru_entry.get()); + } + } + + fn handle_set_replica_config_signal(&mut self, signal: SetReplicaConfigSignal) { + info!("setting replica config to {:#}", &signal.config); + self.config = signal.config; + if let Some(partition) = &mut self.partition { + partition.group.set_config(&self.config); + } + } + + fn handle_reset_peer_signal(&mut self, signal: ResetPeerSignal) { + let node_id: NodeId = signal.peer_node_id.into(); + warn!("resetting peer {}", &node_id); + if let Some(partition) = &mut self.partition { + if let Some(remote_group) = partition.data.xfer_state_mut().remote_group_mut() { + remote_group.reset_peer(&node_id); + } + if &node_id == self.peers.our_node_id() { + if let Some(raft_message) = partition.group.raft.timeout() { + partition.group.send_raft_message(raft_message); + } + self.raft_step(); + } else if partition.group.raft.peers().contains(&node_id) { + if let Some(raft_message) = partition.group.raft.reset_peer(node_id) { + partition.group.send_raft_message(raft_message); + } + self.raft_step(); + } + } + } + + fn handle_set_verbose_logging_signal(&mut self, signal: SetVerboseLoggingSignal) { + crate::logging::set_verbose_logging_enabled(signal.verbose_logging); + } + + // + // replica messages + // + + fn replica_message(&mut self, replica_msg: ReplicaToReplicaMessage, from: NodeId) { + match replica_msg.inner { + Some(replica_to_replica_message::Inner::RaftMessage(raft_msg)) => + self.handle_raft_message(raft_msg, from), + Some(replica_to_replica_message::Inner::CreateRaftGroupRequest(request)) => + self.handle_create_raft_group_request(request, from), + Some(replica_to_replica_message::Inner::EnclaveGetQuoteRequest(request)) => + self.handle_enclave_get_quote_request(request, from), + Some(replica_to_replica_message::Inner::EnclaveGetQuoteReply(reply)) => + self.handle_enclave_get_quote_reply(reply, from), + + Some(replica_to_replica_message::Inner::XferRequest(request)) => + self.handle_xfer_request(request, from), + Some(replica_to_replica_message::Inner::XferReply(reply)) => + self.handle_xfer_reply(reply, from), + Some(replica_to_replica_message::Inner::XferChunkRequest(request)) => + self.handle_xfer_chunk_request(request, from), + Some(replica_to_replica_message::Inner::XferChunkReply(reply)) => + self.handle_xfer_chunk_reply(reply, from), + Some(replica_to_replica_message::Inner::XferErrorNotLeader(xfer_error)) => + self.handle_xfer_error_not_leader(xfer_error, from), + None => (), + } + } + + fn handle_raft_message(&mut self, raft_msg: RaftMessage, from: NodeId) { + if let Some(partition) = &mut self.partition { + if partition.group.is_authorized(&from) { + if let Some(reply) = partition.group.raft.receive(raft_msg, from) { + partition.group.send_raft_message(reply); + } + self.raft_step(); + } else { + warn!("dropped raft message from unauthorized replica {}: {}", &from, &raft_msg); + } + } + } + + fn handle_create_raft_group_request(&mut self, request: CreateRaftGroupRequest, from: NodeId) { + info!("received raft group from {}", &from); + let _ignore = self.create_raft_group(request); + } + + fn create_raft_group(&mut self, create_group_request: CreateRaftGroupRequest) -> Result<(), ()> { + let CreateRaftGroupRequest { group_id, service_id, node_ids, config, source_partition } = create_group_request.clone(); + let node_ids: BTreeSet = node_ids.into_iter().map(|node_id| node_id.into()).collect(); + if node_ids.contains(self.node_id()) { + if let Some(partition) = &self.partition { + if partition.group.id() != &group_id { + warn!("tried to start raft group {} on replica already containing partition {}", + &group_id, partition.group.id()); + } + Err(()) + } else { + let range = if source_partition.is_some() { + None + } else { + Some(PartitionKeyRange::new_unbounded()) + }; + + info!("creating replica group {} service {} with range {} and nodes {}", + &group_id, OptionDisplay(service_id.as_ref()), OptionDisplay(range.as_ref()), + ListDisplay(node_ids.iter())); + + let raft_log = RaftLogStorage::new(config.raft_log_data_size.to_usize(), config.raft_log_index_size, self.config.raft_log_index_page_cache_size.to_usize())?; + let raft = RaftState::new(group_id, self.node_id().clone(), node_ids, raft_log, RdRand, self.config.election_timeout_ticks, self.config.heartbeat_timeout_ticks, self.config.replication_chunk_size.to_usize()); + let replica_group = self.connect_to_peers(raft)?; + + let xfer_state = if let Some(source_partition) = source_partition { + XferState::DestinationPartition(self.connect_to_source(source_partition)?) + } else { + XferState::None + }; + + self.partition = Some(Partition { + group: replica_group, + data: PartitionData::new(config.storage_size.to_usize(), service_id, range, xfer_state), + create_group_request, + }); + + self.raft_step(); + Ok(()) + } + } else { + warn!("tried to start raft group {} not containing us", &group_id); + Err(()) + } + } + + fn connect_to_peers(&mut self, raft: RaftState) + -> Result { + let mut remotes = Vec::new(); + let our_node_id = self.peers.our_node_id().clone(); + for peer_node_id in raft.peers().iter() { + match self.peers.start_peer(peer_node_id.clone(), NodeType::Replica, RemoteAuthorizationType::Mutual) { + Ok(peer_entry) => { + let sender = peer_entry.remote().sender().clone(); + if *peer_node_id < our_node_id { + info!("connecting to peer replica {}", &peer_node_id); + match peer_entry.connect(PeerState::new_replica) { + Ok(_peer) => (), + Err((peer_entry, _mapper)) => { + error!("aborting starting group due to error connecting to {}", + peer_entry.remote().id()); + return Err(()); + } + } + } else { + peer_entry.insert(PeerState::new_replica); + } + remotes.push(RemoteReplicaState { + sender, + }); + } + Err(Some(PeerState::Replica { remote, .. })) => { + remotes.push(RemoteReplicaState { + sender: remote.sender().clone(), + }); + } + Err(Some(PeerState::Frontend { .. })) | + Err(None) => { + error!("started group with {} when it's already connected as a frontend!", peer_node_id); + return Err(()); + } + } + } + Ok(ReplicaGroupState::new(raft, remotes.into())) + } + + fn connect_to_source(&mut self, source_partition: SourcePartitionConfig) -> Result { + let desired_range = match PartitionKeyRange::try_from_pb(&source_partition.range) { + Ok(desired_range) => desired_range, + Err(()) => { + error!("started replica group with source partition config containing invalid range: {}", &source_partition); + return Err(()); + } + }; + let mut remotes: Vec = Vec::with_capacity(source_partition.node_ids.len()); + for source_node_id_vec in source_partition.node_ids { + let source_node_id: NodeId = source_node_id_vec[..].into(); + info!("connecting to source replica {}", &source_node_id); + + let sender = match self.peers.start_peer(source_node_id.clone(), NodeType::Replica, RemoteAuthorizationType::Mutual) { + Ok(peer_entry) => { + let sender = peer_entry.remote().sender().clone(); + match peer_entry.connect(PeerState::new_replica) { + Ok(_peer) => (), + Err((peer_entry, _)) => { + error!("error initiating connection to source replica {}", peer_entry.remote().id()); + return Err(()); + } + } + sender + } + Err(Some(PeerState::Replica { remote, .. })) => { + remote.sender().clone() + } + Err(Some(PeerState::Frontend { .. })) | + Err(None) => { + error!("source replica {} was already connected as a frontend!", NodeId::from(source_node_id_vec)); + return Err(()); + } + }; + if !remotes.iter().any(|remote| remote.id() == sender.id()) { + remotes.push(sender); + } + } + let remote_group = RemoteGroupState::new("source partition".to_string(), remotes); + + info!("started destination partition for range {} with {}", &desired_range, &remote_group); + + Ok(XferSource::new(remote_group, desired_range)) + } + + fn handle_enclave_get_quote_request(&mut self, _request: EnclaveGetQuoteRequest, from: NodeId) { + self.peers.request_quote(from); + } + + fn handle_enclave_get_quote_reply(&mut self, reply: EnclaveGetQuoteReply, from: NodeId) { + self.peers.request_attestation(reply.sgx_quote, from); + } + + fn handle_xfer_request(&mut self, xfer_request: XferRequest, from: NodeId) { + match &mut self.partition { + Some(_) => (), + None => { + warn!("received XferRequest from {} without having a partition: {}", &from, &xfer_request); + return; + } + } + match PartitionKeyRange::try_from_pb(&xfer_request.full_range) { + Ok(_) => (), + Err(()) => { + warn!("received XferRequest from {} with invalid range: {}", &from, &xfer_request); + return; + } + } + + let start_xfer_txn = TransactionData { + inner: Some(transaction_data::Inner::StartXfer(StartXferTransaction { + from_node_id: from.to_vec(), + xfer_request, + })), + }; + self.request_transaction(start_xfer_txn); + } + + fn handle_xfer_reply(&mut self, xfer_reply: XferReply, from: NodeId) { + let xfer_source = match self.partition.as_mut().map(|partition| partition.data.xfer_state_mut()) { + Some(XferState::DestinationPartition(xfer_source)) => xfer_source, + _ => { + warn!("received unexpected XferReply from {}", &from); + self.send_xfer_error_not_leader(from); + return; + } + }; + if xfer_source.remote_group().contains_authorized_node(&from) { + xfer_source.received_reply(&PendingXferRequestId::XferRequest); + let txn = TransactionData { + inner: Some(transaction_data::Inner::SetSid(SetSidTransaction { + from_node_id: from.to_vec(), + service_id: xfer_reply.service, + })), + }; + self.request_transaction(txn); + } else { + warn!("received XferReply from unknown source node {}", &from); + self.send_xfer_error_not_leader(from); + } + } + + fn handle_xfer_chunk_request(&mut self, xfer_chunk_request: XferChunkRequest, from: NodeId) { + let xfer_source = match self.partition.as_mut().map(|partition| partition.data.xfer_state_mut()) { + Some(XferState::DestinationPartition(xfer_source)) => xfer_source, + _ => { + warn!("received unexpected XferChunkRequest from {}", &from); + self.send_xfer_error_not_leader(from); + return; + } + }; + if xfer_source.remote_group().contains_authorized_node(&from) { + let new_last = xfer_chunk_request.chunk_range.last.clone(); + let txn = TransactionData { + inner: Some(transaction_data::Inner::ApplyChunk(ApplyChunkTransaction { + from_node_id: from.to_vec(), + xfer_chunk_request, + xfer_chunk_reply: XferChunkReply { + new_last, + chunk_size: self.config.transfer_chunk_size, + }, + })), + }; + self.request_transaction(txn); + } else { + warn!("received XferChunkRequest from unknown source node {}", &from); + self.send_xfer_error_not_leader(from); + } + } + + fn handle_xfer_chunk_reply(&mut self, xfer_chunk_reply: XferChunkReply, from: NodeId) { + if let Some(partition) = &mut self.partition { + if let XferState::SourcePartition(xfer_destination) = partition.data.xfer_state_mut() { + xfer_destination.received_reply(&PendingXferRequestId::XferReply); + xfer_destination.received_reply(&PendingXferRequestId::XferChunkRequest { + new_last: xfer_chunk_reply.new_last.clone(), + }); + } + } + + let partition = match &self.partition { + Some(partition) => partition, + None => { + warn!("received unexpected XferChunkReply from {}", &from); + self.send_xfer_error_not_leader(from); + return; + } + }; + let xfer_destination = match partition.data.xfer_state() { + XferState::SourcePartition(xfer_destination) => xfer_destination, + _ => { + warn!("received unexpected XferChunkReply from {}", &from); + self.send_xfer_error_not_leader(from); + return; + } + }; + + if xfer_destination.remote_group().contains_authorized_node(&from) { + let chunk_size = self.config.replication_chunk_size.min(xfer_chunk_reply.chunk_size); + let chunk_last = partition.data.next_chunk_last(chunk_size, xfer_destination.full_range()); + let txn = TransactionData { + inner: Some(transaction_data::Inner::RemoveChunk(RemoveChunkTransaction { + from_node_id: from.to_vec(), + xfer_chunk_reply, + chunk_last, + })), + }; + self.request_transaction(txn); + } else { + warn!("received XferChunkReply from unknown source node {}", &from); + self.send_xfer_error_not_leader(from); + } + } + + fn handle_xfer_error_not_leader(&mut self, xfer_error_not_leader: XferErrorNotLeader, from: NodeId) { + let term: TermId = xfer_error_not_leader.term; + let leader: Option = xfer_error_not_leader.leader_node_id.map(NodeId::from); + if let Some(partition) = &mut self.partition { + if let Some(remote_group) = partition.data.xfer_state_mut().remote_group_mut() { + remote_group.remote_not_leader(term, leader.as_ref(), &from); + } + } + } + + // + // frontend messages + // + + fn frontend_message(&mut self, msg: FrontendToReplicaMessage, from: NodeId) { + match msg.inner { + Some(frontend_to_replica_message::Inner::TransactionRequest(req)) => + self.handle_transaction_request(req, from), + Some(frontend_to_replica_message::Inner::EnclaveGetQuoteRequest(request)) => + self.handle_enclave_get_quote_request(request, from), + None => (), + } + } + + fn handle_transaction_request(&mut self, request: TransactionRequest, from_node_id: NodeId) { + if let Some(request_data) = request.data { + match self.accept_transaction_request(request_data) { + Ok(transaction) => { + let frontend_request_txn = TransactionData { + inner: Some(transaction_data::Inner::FrontendRequest(FrontendRequestTransaction { + from_node_id: from_node_id.to_vec(), + request_id: request.request_id, + transaction: Some(transaction), + })), + }; + self.request_transaction(frontend_request_txn); + } + Err(early_response) => { + if let Some(from) = self.peers.get_frontend(&from_node_id) { + send_transaction_reply(from, request.request_id, early_response); + } + } + } + } else { + if let Some(from) = self.peers.get_frontend(&from_node_id) { + let _ignore = from.send(Rc::new(ReplicaToFrontendMessage { + inner: Some(replica_to_frontend_message::Inner::TransactionReply(TransactionReply { + request_id: request.request_id, + data: None, + })), + })); + } + } + } + + fn accept_transaction_request(&mut self, request_data: transaction_request::Data) + -> Result { + let partition = match &mut self.partition { + Some(partition) => partition, + None => { + return Err(transaction_reply::Data::NotLeader(TransactionErrorNotLeader { + leader_node_id: None, + term: Default::default(), + })); + } + }; + + if !partition.group.raft.is_leader() { + let (leader, &term) = partition.group.raft.leader(); + return Err(transaction_reply::Data::NotLeader(TransactionErrorNotLeader { + leader_node_id: leader.map(|leader| leader.to_vec()), + term, + })); + } + + match request_data { + transaction_request::Data::Create(create_backup_request) => { + if let Ok(backup_id) = PartitionKey::try_from_pb(&create_backup_request.backup_id) { + if let Some((nonce, tries)) = partition.data.get_entry_nonce(&backup_id) { + Err(transaction_reply::Data::CreateBackupReply(CreateBackupReply { + nonce: nonce.to_combined().to_vec(), + tries: tries.map(u16::from).map(u32::from), + })) + } else { + Ok(frontend_request_transaction::Transaction::Create(CreateBackupTransaction { + backup_id: create_backup_request.backup_id, + new_creation_nonce: generate_nonce_16(), + new_nonce: generate_nonce_16(), + })) + } + } else { + Err(transaction_reply::Data::InvalidRequest(TransactionErrorInvalidRequest {})) + } + } + transaction_request::Data::Backup(backup_request) => { + let min_attestation = AttestationParameters::new(Duration::from_secs(backup_request.valid_from)); + let our_service_id = partition.data.service_id_bytes(); + let request_service_id = backup_request.service_id.as_ref().map(|service_id: &Vec| &service_id[..]); + let request_nonce = Self::decode_transaction_request_nonce(backup_request.nonce)?; + + if (our_service_id.is_none() || + (request_service_id.is_some() && request_service_id != our_service_id)) + { + Err(transaction_reply::Data::ServiceIdMismatch(TransactionErrorServiceIdMismatch {})) + } else if min_attestation > partition.group.attestation() { + Err(transaction_reply::Data::ClientResponse(kbupd_client::Response { + backup: Some(kbupd_client::BackupResponse { + status: Some(kbupd_client::backup_response::Status::NotYetValid.into()), + nonce: None, + }), + restore: None, + delete: None, + })) + } else { + Ok(frontend_request_transaction::Transaction::Backup(BackupTransaction { + backup_id: backup_request.backup_id, + old_nonce: request_nonce.current_nonce.to_vec(), + new_creation_nonce: generate_nonce_16(), + new_nonce: generate_nonce_16(), + data: backup_request.data, + pin: backup_request.pin, + tries: backup_request.tries, + })) + } + } + transaction_request::Data::Restore(restore_request) => { + let min_attestation = AttestationParameters::new(Duration::from_secs(restore_request.valid_from)); + let our_service_id = partition.data.service_id_bytes(); + let request_service_id = restore_request.service_id.as_ref().map(|service_id: &Vec| &service_id[..]); + let request_nonce = Self::decode_transaction_request_nonce(restore_request.nonce)?; + + if (our_service_id.is_none() || + (request_service_id.is_some() && request_service_id != our_service_id)) + { + Err(transaction_reply::Data::ServiceIdMismatch(TransactionErrorServiceIdMismatch {})) + } else if min_attestation > partition.group.attestation() { + Err(transaction_reply::Data::ClientResponse(kbupd_client::Response { + backup: None, + restore: Some(kbupd_client::RestoreResponse { + status: Some(kbupd_client::restore_response::Status::NotYetValid.into()), + nonce: None, + data: None, + tries: None, + }), + delete: None, + })) + } else { + Ok(frontend_request_transaction::Transaction::Restore(RestoreTransaction { + backup_id: restore_request.backup_id, + creation_nonce: request_nonce.creation_nonce.to_vec(), + old_nonce: request_nonce.current_nonce.to_vec(), + new_nonce: generate_nonce_16(), + pin: restore_request.pin, + })) + } + } + transaction_request::Data::Delete(delete_backup_request) => { + let our_service_id = partition.data.service_id_bytes(); + let request_service_id = delete_backup_request.service_id.as_ref().map(|service_id: &Vec| &service_id[..]); + if (our_service_id.is_none() || + (request_service_id.is_some() && request_service_id != our_service_id)) + { + Err(transaction_reply::Data::ServiceIdMismatch(TransactionErrorServiceIdMismatch {})) + } else { + Ok(frontend_request_transaction::Transaction::Delete(DeleteBackupTransaction { + backup_id: delete_backup_request.backup_id, + })) + } + } + } + } + + fn decode_transaction_request_nonce(combined_nonce: Vec) -> Result { + let combined_nonce: &[u8; 32] = (&combined_nonce[..].try_into()) + .map_err(|_| transaction_reply::Data::InvalidRequest(TransactionErrorInvalidRequest {}))?; + Ok(RequestNonce::from_combined(*combined_nonce)) + } + + // + // raft + // + + fn raft_step(&mut self) { + // apply committed transactions + if let Some(partition) = &mut self.partition { + let is_leader = partition.group.raft.is_leader(); + while let Some(encoded_transaction) = partition.group.raft.take_committed_transaction() { + let log_index = partition.group.raft.last_applied().id; + + let txn = match TransactionData::decode(&encoded_transaction.data[..]) { + Ok(transaction) => transaction, + Err(_) => panic!("error decoding committed raft transaction"), + }; + let txn_info = if let Some(txn_inner) = txn.inner { + Some(partition.data.perform_transaction(txn_inner, &mut self.peers, &mut partition.group, is_leader)) + } else { + None + }; + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::EnclaveTransactionSignal(EnclaveTransactionSignal { + log_index, + transaction: txn_info, + })), + }); + } + } + + // cancel transactions + if let Some(partition) = &mut self.partition { + let mut frontends_sent_to = BTreeSet::new(); + for encoded_transaction in partition.group.raft.log_mut().take_cancelled() { + if let Ok(transaction) = TransactionData::decode(&encoded_transaction.get()[..]) { + let should_send = match &transaction.inner { + Some(transaction_data::Inner::FrontendRequest(FrontendRequestTransaction { from_node_id, .. })) => { + frontends_sent_to.insert(from_node_id.clone()) + } + _ => true, + }; + if should_send { + self.cancel_transaction(transaction, None); + } + } else { + error!("error decoding cancelled transaction!"); + } + } + } + + // append entries + if let Some(partition) = &mut self.partition { + for peer in partition.group.raft.peers().clone() { + if partition.group.is_authorized(&peer) { + if let Some(append_request) = partition.group.raft.append_entries(peer) { + partition.group.send_raft_message(append_request); + } + } + } + } + + if let Some(partition) = &mut self.partition { + if partition.group.raft.is_leader() { + partition.data.send_pending_xfer_messages(); + } + } + } + + fn request_transaction(&mut self, transaction: TransactionData) { + if let Some(partition) = &mut self.partition { + let mut encoded_transaction = SecretValue::new(Vec::with_capacity(transaction.encoded_len())); + let request_transaction_result = if let Ok(()) = transaction.encode(encoded_transaction.get_mut()) { + partition.group.raft.client_request(encoded_transaction.into_inner()) + } else { + error!("error encoding transaction!"); + Err(()) + }; + match request_transaction_result { + Ok(()) => { + self.raft_step(); + } + Err(()) => { + let transaction_error = transaction_reply::Data::InternalError(TransactionErrorInternalError {}); + self.cancel_transaction(transaction, Some(transaction_error)) + } + } + } else { + self.cancel_transaction(transaction, None); + } + } + + fn cancel_transaction(&mut self, transaction: TransactionData, transaction_error: Option) { + match transaction.inner { + Some(transaction_data::Inner::FrontendRequest(client_req_txn)) => { + let leader: Option<(Option<&NodeId>, &TermId)> = self.partition.as_ref().map(|partition| partition.group.raft.leader()); + let from_node_id: NodeId = client_req_txn.from_node_id.into(); + if let Some(from) = self.peers.get_frontend(&from_node_id) { + let transaction_error = match transaction_error { + Some(transaction_error) => transaction_error, + None => transaction_reply::Data::NotLeader(TransactionErrorNotLeader { + leader_node_id: leader.and_then(|leader| leader.0).map(|leader| leader.to_vec()), + term: leader.map(|leader| leader.1).cloned().unwrap_or_default(), + }), + }; + send_transaction_reply(from, client_req_txn.request_id, transaction_error); + } + } + Some(transaction_data::Inner::StartXfer(request)) => { + let from_node_id: NodeId = request.from_node_id.into(); + self.send_xfer_error_not_leader(from_node_id); + } + Some(transaction_data::Inner::SetSid(request)) => { + let from_node_id: NodeId = request.from_node_id.into(); + self.send_xfer_error_not_leader(from_node_id); + } + Some(transaction_data::Inner::RemoveChunk(request)) => { + let from_node_id: NodeId = request.from_node_id.into(); + self.send_xfer_error_not_leader(from_node_id); + } + Some(transaction_data::Inner::ApplyChunk(request)) => { + let from_node_id: NodeId = request.from_node_id.into(); + self.send_xfer_error_not_leader(from_node_id); + } + Some(transaction_data::Inner::PauseXfer(request)) => { + info!("cannot pause partitioning process on non-leader"); + send_untrusted_xfer_reply(request.request_id, UntrustedXferReplyStatus::NotLeader); + } + Some(transaction_data::Inner::ResumeXfer(request)) => { + info!("cannot resume partitioning process on non-leader"); + send_untrusted_xfer_reply(request.request_id, UntrustedXferReplyStatus::NotLeader); + } + Some(transaction_data::Inner::FinishXfer(request)) => { + info!("cannot finish partitioning process on non-leader"); + send_untrusted_xfer_reply(request.request_id, UntrustedXferReplyStatus::NotLeader); + } + Some(transaction_data::Inner::SetTime(_request)) => { + } + None => { + } + } + } + + fn send_xfer_error_not_leader(&self, from_node_id: NodeId) { + let leader = self.partition.as_ref().map(|partition| partition.group.raft.leader()); + if let Some(PeerState::Replica { remote, .. }) = self.peers.get_peer(&from_node_id) { + let r2r_msg = Rc::new(ReplicaToReplicaMessage { + inner: Some(replica_to_replica_message::Inner::XferErrorNotLeader(XferErrorNotLeader { + leader_node_id: leader.and_then(|leader| leader.0).map(|leader| leader.to_vec()), + term: leader.map(|leader| leader.1).cloned().unwrap_or_default(), + })), + }); + let _ignore = remote.send(r2r_msg); + } + } +} + +// +// RaftGroupId impls +// + +fn generate_group_id() -> RaftGroupId { + RaftGroupId { id: RdRand.rand_bytes(vec![0; 32]) } +} + +// +// ServiceId impls +// + +fn generate_service_id() -> ServiceId { + ServiceId { id: RdRand.rand_bytes(vec![0; 32]) } +} + +// +// PeerState impls +// + +impl PeerState { + fn new_replica(remote: RemoteState) -> Self { + let authorized = remote.attestation().is_some(); + PeerState::Replica { + remote, + authorized, + } + } + #[must_use] + fn authorize(&mut self) -> Option { + match self { + PeerState::Frontend { .. } => { + None + } + PeerState::Replica { remote, authorized } => { + if !*authorized { + let maybe_attestation = remote.attestation(); + if maybe_attestation.is_some() { + *authorized = true; + } + maybe_attestation + } else { + None + } + } + } + } +} + +impl Peer for PeerState { + type Message = PeerMessage; + fn remote_mut(&mut self) -> &mut dyn Remote { + match self { + PeerState::Frontend { remote, .. } => remote, + PeerState::Replica { remote, .. } => remote, + } + } + fn recv(&mut self, msg_data: &[u8]) -> Result { + match self { + PeerState::Frontend { remote, .. } => { + remote.recv(msg_data).map(PeerMessage::Frontend) + } + PeerState::Replica { remote, .. } => { + remote.recv(msg_data).map(PeerMessage::Replica) + } + } + } + fn send_quote_reply(&mut self, reply: EnclaveGetQuoteReply) -> Result<(), ()> { + match self { + PeerState::Frontend { remote, .. } => { + remote.send(Rc::new(ReplicaToFrontendMessage { + inner: Some(replica_to_frontend_message::Inner::EnclaveGetQuoteReply(reply)) + })) + } + PeerState::Replica { remote, .. } => { + remote.send(Rc::new(ReplicaToReplicaMessage { + inner: Some(replica_to_replica_message::Inner::EnclaveGetQuoteReply(reply)) + })) + } + } + } +} + +impl PeerManager { + fn get_frontend(&mut self, node_id: &NodeId) -> Option<&mut dyn RemoteMessageSender> { + match self.get_peer_mut(node_id) { + Some(PeerState::Frontend { remote, .. }) => Some(remote), + _ => None, + } + } +} + +// +// RemoteSender impls +// + +impl RemoteGroupNode for RemoteSender { + fn request_quote(&mut self, request: EnclaveGetQuoteRequest) -> Result<(), ()> { + self.send(Rc::new(ReplicaToReplicaMessage { + inner: Some(replica_to_replica_message::Inner::EnclaveGetQuoteRequest(request)), + })) + } +} + +// +// utils +// + +fn generate_nonce_16() -> Vec { + RdRand.rand_bytes(vec![0; 16]) +} + + +fn send_transaction_reply(from: &mut dyn RemoteMessageSender, + request_id: u64, + data: transaction_reply::Data) +{ + let _ignore = from.send(Rc::new(ReplicaToFrontendMessage { + inner: Some(replica_to_frontend_message::Inner::TransactionReply(TransactionReply { + request_id, + data: Some(data), + })), + })); +} + +fn send_untrusted_xfer_reply(request_id: u64, status: UntrustedXferReplyStatus) { + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::UntrustedXferReply(UntrustedXferReply { + request_id, + status: status.into(), + })), + }); +} + +// +// tests +// + +#[cfg(test)] +mod tests { + use super::*; + use crate::ffi::mocks; + use crate::ffi::ecalls; + use mockers::*; + + fn init(start_replica_req: StartReplicaRequest) -> ReplicaState { + ReplicaState::init(start_replica_req) + } + + fn valid_range() -> PartitionKeyRangePb { + PartitionKeyRangePb { + first: BackupId { id: vec![0x00; 32] }, + last: BackupId { id: vec![0xFF; 32] }, + } + } + + #[test] + fn init_test() { + let scenario = Scenario::new(); + let expected_enclave_messages: Vec>> = vec![ + Box::new(arg!(enclave_message::Inner::StartReplicaReply(StartReplicaReply { + .. + }))) + ]; + mocks::expect_enclave_messages(&scenario, expected_enclave_messages); + init(StartReplicaRequest { + config: Default::default(), + }); + ecalls::kbupd_send_flush(); + } + + #[test] + fn start_new_group_no_peers() { + let scenario = Scenario::new(); + let expected_enclave_messages: Vec>> = vec![ + Box::new(arg!(enclave_message::Inner::StartReplicaReply(StartReplicaReply { + .. + }))), + Box::new(arg!(enclave_message::Inner::StartReplicaGroupReply(StartReplicaGroupReply { + service_id: Some(_), + group_id: Some(_), + .. + }))) + ]; + mocks::expect_enclave_messages(&scenario, expected_enclave_messages); + let mut state = init(StartReplicaRequest { + config: Default::default(), + }); + state.handle_start_replica_group_request(StartReplicaGroupRequest { + peer_node_ids: vec![], + source_partition: None, + config: Default::default(), + }); + ecalls::kbupd_send_flush(); + } + + #[test] + fn start_new_group_with_peer() { + let scenario = Scenario::new(); + let expected_enclave_messages: Vec>> = vec![ + Box::new(arg!(enclave_message::Inner::StartReplicaReply(StartReplicaReply { + .. + }))), + Box::new(arg!(enclave_message::Inner::StartReplicaGroupReply(StartReplicaGroupReply { + service_id: Some(_), + group_id: Some(_), + .. + }))), + Box::new(arg!(enclave_message::Inner::GetQeInfoRequest(GetQeInfoRequest { + .. + }))), + ]; + mocks::expect_enclave_messages(&scenario, expected_enclave_messages); + let mut state = init(StartReplicaRequest { + config: Default::default(), + }); + state.handle_start_replica_group_request(StartReplicaGroupRequest { + peer_node_ids: vec![vec![0; 32]], + source_partition: None, + config: Default::default(), + }); + state.handle_timer_tick_signal(Default::default()); + ecalls::kbupd_send_flush(); + } + + #[test] + fn start_new_xfer_group_no_peers() { + let scenario = Scenario::new(); + let expected_enclave_messages: Vec>> = vec![ + Box::new(arg!(enclave_message::Inner::StartReplicaReply(StartReplicaReply { + .. + }))), + Box::new(arg!(enclave_message::Inner::StartReplicaGroupReply(StartReplicaGroupReply { + service_id: None, + group_id: Some(_), + .. + }))), + Box::new(arg!(enclave_message::Inner::GetQeInfoRequest(GetQeInfoRequest { + .. + }))), + ]; + mocks::expect_enclave_messages(&scenario, expected_enclave_messages); + let mut state = init(StartReplicaRequest { + config: Default::default(), + }); + state.handle_start_replica_group_request(StartReplicaGroupRequest { + peer_node_ids: vec![vec![0; 32]], + source_partition: Some(SourcePartitionConfig { + range: valid_range(), + node_ids: vec![], + }), + config: Default::default(), + }); + state.handle_timer_tick_signal(Default::default()); + ecalls::kbupd_send_flush(); + } + + #[test] + fn start_new_xfer_group_with_peer() { + let scenario = Scenario::new(); + let expected_enclave_messages: Vec>> = vec![ + Box::new(arg!(enclave_message::Inner::StartReplicaReply(StartReplicaReply { + .. + }))), + Box::new(arg!(enclave_message::Inner::GetQeInfoRequest(GetQeInfoRequest { + .. + }))), + Box::new(arg!(enclave_message::Inner::StartReplicaGroupReply(StartReplicaGroupReply { + service_id: None, + group_id: Some(_), + .. + }))) + ]; + mocks::expect_enclave_messages(&scenario, expected_enclave_messages); + let mut state = init(StartReplicaRequest { + config: Default::default(), + }); + state.handle_start_replica_group_request(StartReplicaGroupRequest { + peer_node_ids: vec![vec![0; 32]], + source_partition: Some(SourcePartitionConfig { + range: valid_range(), + node_ids: vec![vec![0; 32]], + }), + config: Default::default(), + }); + state.handle_timer_tick_signal(Default::default()); + ecalls::kbupd_send_flush(); + } +} diff --git a/enclave/kbupd_enclave/src/service/replica/partition_data.rs b/enclave/kbupd_enclave/src/service/replica/partition_data.rs new file mode 100644 index 0000000..4866a2b --- /dev/null +++ b/enclave/kbupd_enclave/src/service/replica/partition_data.rs @@ -0,0 +1,1214 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::prelude::*; + +use std::convert::{TryInto}; +use std::collections::*; +use std::cmp::*; +use std::mem; +use std::num::*; +use std::time::*; +use std::rc::*; + +use bytes::*; +use num_traits::{ToPrimitive}; +use sgx_ffi::util::{SecretValue}; + +use crate::protobufs::kbupd::*; +use crate::protobufs::kbupd_enclave::*; +use crate::protobufs::kbupd_client; +use crate::protobufs::raft::*; +use crate::remote::*; +use crate::remote_group::*; +use crate::util::*; + +use super::*; + +pub(super) struct PartitionData { + storage: BTreeMap>, + capacity: usize, + service_id: Option, + range: Option, + xfer_state: XferState, +} + +#[allow(clippy::large_enum_variant)] +pub(super) enum XferState { + DestinationPartition(XferSource), + SourcePartition(XferDestination), + None, +} + +pub(super) struct XferSource { + remote_group: RemoteGroupState, + desired_range: PartitionKeyRange, + + cur_xfer_chunk_reply: Option, +} + +pub(super) struct XferDestination { + remote_group_id: RaftGroupId, + remote_group: RemoteGroupState, + full_range: PartitionKeyRange, + chunk_size: u32, + + paused: bool, + inflight: Option, + + cur_xfer_reply: Option, + cur_xfer_chunk_request: Option, +} + +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)] +pub(super) enum PendingXferRequestId { + XferRequest, + XferReply, + XferChunkRequest { + new_last: BackupId, + }, + XferChunkReply { + new_last: BackupId, + }, +} + +#[derive(Clone)] +pub(super) struct PendingXferRequest { + pub id: PendingXferRequestId, + pub message: Rc, + pub min_attestation: Option, +} + +pub(super) struct RequestNonce { + pub creation_nonce: [u8; 16], + pub current_nonce: [u8; 16], +} + +pub(super) struct BackupEntry { + tries: Option, + pin: SecretValue<[u8; 32]>, + creation_nonce: [u8; 16], + current_nonce: [u8; 16], + data: SecretValue<[u8; 32]>, +} + +pub(super) struct XferBackupEntry { + id: PartitionKey, + entry: Box, +} + +pub(super) enum FrontendRequestError { + InvalidRequest, + StorageFull, +} + +impl PartitionData { + pub fn new(capacity: usize, + service_id: Option, + range: Option, + xfer_state: XferState) + -> Self + { + Self { + storage: Default::default(), + capacity, + service_id, + range, + xfer_state, + } + } + + pub fn service_id(&self) -> Option<&ServiceId> { + self.service_id.as_ref() + } + + pub fn service_id_bytes(&self) -> Option<&[u8]> { + self.service_id.as_ref().map(|service_id| &service_id.id[..]) + } + + pub fn xfer_state(&self) -> &XferState { + &self.xfer_state + } + + pub fn xfer_state_mut(&mut self) -> &mut XferState { + &mut self.xfer_state + } + + pub fn range(&self) -> Option<&PartitionKeyRange> { + self.range.as_ref() + } + + pub fn storage_len(&self) -> usize { + self.storage.len() + } + + pub fn xfer_status(&self) -> Option { + match &self.xfer_state { + XferState::SourcePartition(xfer_destination) => { + Some(enclave_replica_partition_status::XferStatus::OutgoingXferStatus(EnclaveOutgoingXferStatus { + group_id: xfer_destination.remote_group_id.id.clone(), + full_xfer_range: xfer_destination.full_range.to_pb(), + current_chunk_range: xfer_destination.inflight.as_ref().map(PartitionKeyRange::to_pb), + paused: xfer_destination.paused, + min_attestation: xfer_destination.cur_xfer_chunk_request.as_ref().and_then(PendingXferRequest::min_attestation), + nodes: xfer_destination.remote_group.status(), + })) + } + XferState::DestinationPartition(xfer_source) => { + Some(enclave_replica_partition_status::XferStatus::IncomingXferStatus(EnclaveIncomingXferStatus { + desired_range: xfer_source.desired_range.to_pb(), + nodes: xfer_source.remote_group.status(), + })) + } + XferState::None => None, + } + } + + pub fn send_pending_xfer_messages(&mut self) { + match &mut self.xfer_state { + XferState::DestinationPartition(xfer_source) => { + if let Some(cur_xfer_chunk_reply) = xfer_source.cur_xfer_chunk_reply.clone() { + let _ignore = xfer_source.remote_group_mut().send(cur_xfer_chunk_reply); + } + } + XferState::SourcePartition(xfer_destination) => { + if let Some(cur_xfer_reply) = xfer_destination.cur_xfer_reply.clone() { + let _ignore = xfer_destination.remote_group_mut().send(cur_xfer_reply); + } + if let Some(cur_xfer_chunk_request) = xfer_destination.cur_xfer_chunk_request.clone() { + let _ignore = xfer_destination.remote_group_mut().send(cur_xfer_chunk_request); + } + } + XferState::None => (), + } + } + + pub fn get_entry_nonce(&mut self, backup_id: &PartitionKey) -> Option<(RequestNonce, Option)> { + if let Some(backup) = self.storage.get(backup_id) { + Some((backup.request_nonce(), backup.tries)) + } else { + None + } + } + + pub fn perform_transaction(&mut self, + txn: transaction_data::Inner, + peers: &mut PeerManager, + group: &mut ReplicaGroupState, + is_leader: bool) + -> enclave_transaction_signal::Transaction + { + match txn { + transaction_data::Inner::FrontendRequest(txn) => + enclave_transaction_signal::Transaction::FrontendRequest( + self.perform_client_transaction(txn, peers, is_leader) + ), + transaction_data::Inner::StartXfer(txn) => + enclave_transaction_signal::Transaction::StartXfer( + self.perform_start_xfer_transaction(txn, peers) + ), + transaction_data::Inner::SetSid(txn) => + enclave_transaction_signal::Transaction::SetSid( + self.perform_set_sid_transaction(txn) + ), + transaction_data::Inner::RemoveChunk(txn) => + enclave_transaction_signal::Transaction::RemoveChunk( + self.perform_remove_chunk_transaction(txn, group) + ), + transaction_data::Inner::ApplyChunk(txn) => + enclave_transaction_signal::Transaction::ApplyChunk( + self.perform_apply_chunk_transaction(txn, group) + ), + transaction_data::Inner::PauseXfer(txn) => + enclave_transaction_signal::Transaction::PauseXfer( + self.perform_pause_xfer_transaction(txn) + ), + transaction_data::Inner::ResumeXfer(txn) => + enclave_transaction_signal::Transaction::ResumeXfer( + self.perform_resume_xfer_transaction(txn, group) + ), + transaction_data::Inner::FinishXfer(txn) => + enclave_transaction_signal::Transaction::FinishXfer( + self.perform_finish_xfer_transaction(txn) + ), + transaction_data::Inner::SetTime(txn) => + enclave_transaction_signal::Transaction::SetTime( + self.perform_set_time_transaction(txn, group) + ), + } + } + + fn perform_client_transaction(&mut self, + txn: FrontendRequestTransaction, + peers: &mut PeerManager, + is_leader: bool) + -> EnclaveFrontendRequestTransaction + { + let backup_id = txn.backup_id().cloned(); + let txn_reply_data = if let Some(backup_id) = txn.backup_id() { + if self.check_xfer_in_progress(backup_id) { + transaction_reply::Data::XferInProgress(TransactionErrorXferInProgress {}) + } else if !self.range_contains(backup_id) { + let maybe_our_range = if let XferState::SourcePartition(xfer_destination) = &self.xfer_state { + if let (Some(our_authoritative_range), Some(inflight)) = (&self.range, &xfer_destination.inflight) { + match PartitionKeyRange::new(*inflight.first(), *our_authoritative_range.last()) { + Ok(our_range) => Some(our_range), + Err(()) => { + error!("our authoritative range {} is less than inflight range {}!", our_authoritative_range, inflight); + None + } + } + } else if let Some(our_authoritative_range) = &self.range { + Some(*our_authoritative_range) + } else if let Some(inflight_range) = &xfer_destination.inflight { + Some(*inflight_range) + } else { + None + } + } else { + self.range + }; + + let new_partition = if let XferState::SourcePartition(xfer_destination) = &self.xfer_state { + let other_range = if let Some(our_range) = &maybe_our_range { + if let Some(other_last) = our_range.first().checked_sub(1) { + match PartitionKeyRange::new(*xfer_destination.full_range.first(), other_last) { + Ok(other_range) => Some(other_range.to_pb()), + Err(()) => None, + } + } else { + None + } + } else { + Some(xfer_destination.full_range.to_pb()) + }; + Some(PartitionConfig { + group_id: xfer_destination.remote_group_id.id.clone(), + range: other_range, + node_ids: xfer_destination.remote_group.get_remotes().into_iter().map(|r| r.to_vec()).collect(), + }) + } else { + info!("Wrong partition for {}, but we don't know the right one!", backup_id); + None + }; + + transaction_reply::Data::WrongPartition(TransactionErrorWrongPartition { + range: maybe_our_range.map(|r| r.to_pb()), + new_partition, + }) + } else if let Some(txn_data) = txn.transaction { + match self.perform_frontend_request(txn_data) { + Ok(response_data) => response_data, + Err(FrontendRequestError::InvalidRequest) => + transaction_reply::Data::InvalidRequest(TransactionErrorInvalidRequest {}), + Err(FrontendRequestError::StorageFull) => + transaction_reply::Data::InternalError(TransactionErrorInternalError {}), + } + } else { + transaction_reply::Data::InvalidRequest(TransactionErrorInvalidRequest {}) + } + } else { + transaction_reply::Data::WrongPartition(TransactionErrorWrongPartition { + range: self.range.map(|r| r.to_pb()), + new_partition: None, + }) + }; + + let txn_info = if let Some(backup_id) = backup_id { + enclave_frontend_request_transaction::Transaction::from_reply(backup_id, &txn_reply_data) + } else { + enclave_frontend_request_transaction::Transaction::InvalidRequest(EnclaveTransactionErrorInvalidRequest {}) + }; + + if is_leader { + let from_node_id: NodeId = txn.from_node_id.clone().into(); + if let Some(from) = peers.get_frontend(&from_node_id) { + send_transaction_reply(from, txn.request_id, txn_reply_data); + } + } + + EnclaveFrontendRequestTransaction { + transaction: Some(txn_info), + } + } + + fn check_xfer_in_progress(&self, id: &BackupId) -> bool { + if let XferState::SourcePartition(xfer_destination) = &self.xfer_state { + if let Some(inflight) = &xfer_destination.inflight { + inflight.contains_id(id) + } else { + false + } + } else { + false + } + } + + fn range_contains(&self, backup_id: &BackupId) -> bool { + if let Some(range) = &self.range { + range.contains_id(backup_id) + } else { + false + } + } + + fn frontend_request_field_to_array(src: &[u8]) -> Result + where T: AsMut<[u8]> + Default + { + match util::copy_exact(src) { + Ok(array) => Ok(array), + Err(()) => Err(FrontendRequestError::InvalidRequest), + } + } + + fn frontend_request_backup_id(backup_id: &BackupId) -> Result { + match PartitionKey::try_from_pb(backup_id) { + Ok(backup_id) => Ok(backup_id), + Err(()) => Err(FrontendRequestError::InvalidRequest), + } + } + + fn get_or_insert_backup_with(&mut self, backup_id: PartitionKey, create_fun: impl FnOnce() -> Box) + -> Result<&mut BackupEntry, FrontendRequestError> { + let storage_len = self.storage.len(); + match self.storage.entry(backup_id) { + btree_map::Entry::Occupied(backup_entry) => { + Ok(backup_entry.into_mut()) + } + btree_map::Entry::Vacant(backup_entry) => { + if storage_len < self.capacity { + Ok(backup_entry.insert(create_fun())) + } else { + Err(FrontendRequestError::StorageFull) + } + } + } + } + + fn perform_frontend_request(&mut self, txn_data: frontend_request_transaction::Transaction) + -> Result + { + match txn_data { + frontend_request_transaction::Transaction::Create(create_request) => { + let backup_id = Self::frontend_request_backup_id(&create_request.backup_id)?; + let new_creation_nonce = Self::frontend_request_field_to_array(&create_request.new_creation_nonce)?; + let new_nonce = Self::frontend_request_field_to_array(&create_request.new_nonce)?; + let backup = self.get_or_insert_backup_with(backup_id, || Box::new(BackupEntry { + pin: Default::default(), + creation_nonce: new_creation_nonce, + current_nonce: new_nonce, + data: Default::default(), + tries: None, + }))?; + + Ok(transaction_reply::Data::CreateBackupReply(CreateBackupReply { + nonce: backup.request_nonce().to_combined().to_vec(), + tries: backup.tries.map(u16::from).map(u32::from), + })) + } + frontend_request_transaction::Transaction::Backup(backup_request) => { + let backup_id = Self::frontend_request_backup_id(&backup_request.backup_id)?; + let new_creation_nonce = Self::frontend_request_field_to_array(&backup_request.new_creation_nonce)?; + let new_nonce = Self::frontend_request_field_to_array(&backup_request.new_nonce)?; + let tries = backup_request.tries.to_u16().and_then(NonZeroU16::new).ok_or(FrontendRequestError::InvalidRequest)?; + + let backup = self.get_or_insert_backup_with(backup_id, || Box::new(BackupEntry { + pin: Default::default(), + creation_nonce: new_creation_nonce, + current_nonce: new_nonce, + data: Default::default(), + tries: None, + }))?; + + let backup_response = if backup_request.old_nonce == backup.creation_nonce { + kbupd_client::BackupResponse { + status: Some(kbupd_client::backup_response::Status::Ok.into()), + nonce: Some(backup.request_nonce().to_combined().to_vec()), + } + } else if backup_request.old_nonce != backup.current_nonce { + kbupd_client::BackupResponse { + status: Some(kbupd_client::backup_response::Status::AlreadyExists.into()), + nonce: Some(backup.request_nonce().to_combined().to_vec()), + } + } else { + if backup_request.pin.data.len() != backup.pin.get().len() { + return Err(FrontendRequestError::InvalidRequest); + } + if backup_request.data.data.len() != backup.data.get().len() { + return Err(FrontendRequestError::InvalidRequest); + } + backup.pin.get_mut().copy_from_slice(&backup_request.pin.data); + backup.data.get_mut().copy_from_slice(&backup_request.data.data); + backup.creation_nonce = backup.current_nonce; + backup.current_nonce = new_nonce; + backup.tries = Some(tries); + + kbupd_client::BackupResponse { + status: Some(kbupd_client::backup_response::Status::Ok.into()), + nonce: Some(backup.request_nonce().to_combined().to_vec()), + } + }; + Ok(transaction_reply::Data::ClientResponse(kbupd_client::Response { + backup: Some(backup_response), + restore: None, + delete: None, + })) + } + frontend_request_transaction::Transaction::Restore(restore_request) => { + let backup_id = Self::frontend_request_backup_id(&restore_request.backup_id)?; + let restore_response = if let btree_map::Entry::Occupied(mut storage_entry) = self.storage.entry(backup_id) { + let entry = storage_entry.get_mut(); + let new_nonce = Self::frontend_request_field_to_array(&restore_request.new_nonce)?; + + if restore_request.creation_nonce != entry.creation_nonce { + kbupd_client::RestoreResponse { + status: Some(kbupd_client::restore_response::Status::Missing.into()), + nonce: None, + data: None, + tries: None, + } + } else if restore_request.old_nonce != entry.current_nonce { + kbupd_client::RestoreResponse { + status: Some(kbupd_client::restore_response::Status::NonceMismatch.into()), + nonce: Some(entry.request_nonce().to_combined().to_vec()), + data: None, + tries: entry.tries.map(u16::from).map(u32::from), + } + } else { + entry.current_nonce = new_nonce; + + if entry.tries.is_some() && entry.pin.consttime_eq(&restore_request.pin.data) { + kbupd_client::RestoreResponse { + status: Some(kbupd_client::restore_response::Status::Ok.into()), + nonce: Some(entry.request_nonce().to_combined().to_vec()), + data: Some(entry.data.get().to_vec()), + tries: entry.tries.map(u16::from).map(u32::from), + } + } else if let Some(tries_minus_one) = entry.tries.and_then(|tries: NonZeroU16| tries.get().checked_sub(2)) { + // decrement tries + let tries: NonZeroU16 = tries_minus_one.checked_add(1) + .and_then(NonZeroU16::new) + .unwrap_or_else(|| unreachable!()); + entry.tries = Some(tries); + kbupd_client::RestoreResponse { + status: Some(kbupd_client::restore_response::Status::PinMismatch.into()), + nonce: Some(entry.request_nonce().to_combined().to_vec()), + data: None, + tries: Some(u32::from(tries.get())), + } + } else { + // ran out of tries. erase backup. + storage_entry.remove(); + kbupd_client::RestoreResponse { + status: Some(kbupd_client::restore_response::Status::Missing.into()), + nonce: None, + data: None, + tries: None, + } + } + } + } else { + kbupd_client::RestoreResponse { + status: Some(kbupd_client::restore_response::Status::Missing.into()), + nonce: None, + data: None, + tries: None, + } + }; + Ok(transaction_reply::Data::ClientResponse(kbupd_client::Response { + backup: None, + restore: Some(restore_response), + delete: None, + })) + } + frontend_request_transaction::Transaction::Delete(delete_request) => { + let backup_id = Self::frontend_request_backup_id(&delete_request.backup_id)?; + self.storage.remove(&backup_id); + Ok(transaction_reply::Data::DeleteBackupReply(DeleteBackupReply {})) + } + } + } + + fn perform_start_xfer_transaction(&mut self, + txn: StartXferTransaction, + peers: &mut PeerManager) + -> EnclaveStartXferTransaction + { + let from = NodeId::from(&txn.from_node_id); + + match &self.xfer_state { + XferState::SourcePartition(xfer_destination) => { + if txn.xfer_request.group_id != xfer_destination.remote_group_id { + warn!("received XferRequest from {} while having xfer destination {}: {}", + &from, &xfer_destination.remote_group_id, &txn.xfer_request); + } else { + verbose!("received duplicate XferRequest from {}", &from); + } + return Default::default(); + } + XferState::DestinationPartition(_xfer_source) => { + warn!("received XferRequest from {} while having xfer source: {}", &from, &txn.xfer_request); + return Default::default(); + } + XferState::None => (), + } + + let full_range = match PartitionKeyRange::try_from_pb(&txn.xfer_request.full_range) { + Ok(full_range) => full_range, + Err(()) => { + error!("received XferRequest from {} with invalid range: {}", &from, &txn.xfer_request); + return Default::default(); + } + }; + let our_range = match &self.range { + Some(our_range) => our_range, + None => { + warn!("received XferRequest from {} while having no range: {}", &from, &txn.xfer_request); + return Default::default(); + } + }; + if (our_range.first() != full_range.first() || + !our_range.contains_range(&full_range)) { + warn!("received XferRequest from {} with requested range outside our range {}: {}", + &from, &our_range, &txn.xfer_request); + return Default::default(); + } + + let service_id = match &self.service_id { + Some(service_id) => service_id, + None => { + warn!("received XferRequest from {} while having no service id: {}", &from, &txn.xfer_request); + return Default::default(); + } + }; + + let mut attestations: Vec<(NodeId, AttestationParameters)> = Vec::new(); + let mut senders: Vec = Vec::new(); + for node_id in &txn.xfer_request.node_ids { + match peers.start_peer(node_id.into(), NodeType::Replica, RemoteAuthorizationType::Mutual) { + Ok(peer_entry) => { + senders.push(peer_entry.remote().sender().clone()); + peer_entry.insert(PeerState::new_replica); + } + Err(Some(PeerState::Replica { remote, .. })) => { + if let Some(attestation) = remote.attestation() { + attestations.push((remote.id().clone(), attestation)); + } + senders.push(remote.sender().clone()); + } + Err(Some(PeerState::Frontend { .. })) | + Err(None) => { + error!("started xfer to {} when it's already connected as a frontend!", NodeId::from(node_id)); + } + } + } + + let cur_xfer_reply = PendingXferRequest { + id: PendingXferRequestId::XferReply, + message: Rc::new(ReplicaToReplicaMessage { + inner: Some(replica_to_replica_message::Inner::XferReply(XferReply { + service: service_id.clone(), + })), + }), + min_attestation: None, + }; + let remote_group_id = txn.xfer_request.group_id.clone(); + let group_name = format!("{}", &remote_group_id); + let mut remote_group = RemoteGroupState::new(group_name, senders); + for (replica_node_id, _attestation) in &attestations { + remote_group.remote_authorized(replica_node_id); + } + let chunk_size = txn.xfer_request.chunk_size; + + let display_nodes = txn.xfer_request.node_ids.iter().map(|node| util::ToHex(node)); + info!("starting xfer of range {} chunk size {} to group {} with nodes {}", + &full_range, &chunk_size, &remote_group_id, ListDisplay(display_nodes)); + + self.xfer_state = XferState::SourcePartition(XferDestination { + remote_group_id, + remote_group, + full_range, + chunk_size, + paused: true, + inflight: None, + + cur_xfer_reply: Some(cur_xfer_reply), + cur_xfer_chunk_request: None, + }); + + EnclaveStartXferTransaction {} + } + + fn perform_set_sid_transaction(&mut self, txn: SetSidTransaction) -> EnclaveSetSidTransaction { + if let XferState::DestinationPartition(xfer_source) = &mut self.xfer_state { + xfer_source.received_reply(&PendingXferRequestId::XferRequest); + } + if let Some(service_id) = &self.service_id { + if service_id != &txn.service_id { + error!("tried to set service id {} on partition already having service id {}", + &txn.service_id, &service_id); + } + Default::default() + } else { + self.service_id = Some(txn.service_id.clone()); + + EnclaveSetSidTransaction { + service_id: Some(txn.service_id), + } + } + } + + fn perform_remove_chunk_transaction(&mut self, txn: RemoveChunkTransaction, group: &mut ReplicaGroupState) -> EnclaveRemoveChunkTransaction { + if let XferState::SourcePartition(xfer_destination) = &mut self.xfer_state { + xfer_destination.received_reply(&PendingXferRequestId::XferReply); + xfer_destination.received_reply(&PendingXferRequestId::XferChunkRequest { + new_last: txn.xfer_chunk_reply.new_last.clone(), + }); + + if let Some(inflight) = &xfer_destination.inflight { + if inflight.last() == &txn.xfer_chunk_reply.new_last { + xfer_destination.inflight = None; + } else { + warn!("dropping out of order RemoveChunkTransaction {} expecting {}", + &txn.xfer_chunk_reply.new_last, &inflight); + } + } + + xfer_destination.chunk_size = txn.xfer_chunk_reply.chunk_size; + + let chunk_range = self.remove_chunk(&txn.chunk_last, group); + EnclaveRemoveChunkTransaction { + chunk_range: chunk_range.as_ref().map(PartitionKeyRange::to_pb), + } + } else { + warn!("dropping unexpected RemoveChunkTransaction {}", &txn.xfer_chunk_reply.new_last); + Default::default() + } + } + + pub fn next_chunk_last(&self, chunk_size: u32, full_range: &PartitionKeyRange) -> BackupId { + let chunk_count = (chunk_size / XferBackupEntry::encoded_len()).max(1); + let chunk_range = self.storage.range(full_range).take(chunk_count.to_usize()); + if let Some((chunk_last, _value)) = chunk_range.last() { + chunk_last.to_pb() + } else { + full_range.to_pb().last + } + } + + fn remove_chunk(&mut self, chunk_last: &BackupId, group: &ReplicaGroupState) -> Option { + let xfer_destination = match &mut self.xfer_state { + XferState::SourcePartition(xfer_destination) => xfer_destination, + _ => { + error!("tried to remove a chunk from a non-source partition"); + return None; + } + }; + let chunk_last = match PartitionKey::try_from_pb(chunk_last) { + Ok(chunk_last) => chunk_last, + Err(()) => { + error!("invalid xfer chunk backup id {}", &chunk_last); + return None; + } + }; + + if xfer_destination.paused { + info!("not sending xfer chunk {} as transfer is paused", &chunk_last); + return None; + } + + if let Some(inflight) = &xfer_destination.inflight { + info!("not sending xfer chunk {} as xfer chunk {} is in progress", &chunk_last, inflight); + return None; + } + + let split_range_res = if let Some(our_range) = &mut self.range { + let mut chunk_range = *our_range; + match chunk_range.split_off_inclusive(&chunk_last) { + Ok(our_new_range) => { + self.range = our_new_range; + Ok(chunk_range) + } + Err(()) => Err(()), + } + } else { + Err(()) + }; + + let chunk_range = match split_range_res { + Ok(chunk_range) => chunk_range, + Err(()) => { + info!("All transfers sent"); + return None; + } + }; + + let maybe_our_new_first = if let Some(range) = &self.range { + Some(range.first()) + } else { + None + }; + + let entries = Self::storage_split_to(&mut self.storage, maybe_our_new_first); + + info!("sending xfer chunk {} length {}", &chunk_range, entries.len()); + + let entry_len = XferBackupEntry::encoded_len().to_usize(); + let mut data = SecretBytes { + data: Vec::with_capacity(entries.len().saturating_mul(entry_len)), + }; + for (id, entry) in entries { + let xfer_entry = XferBackupEntry { id, entry }; + xfer_entry.encode(&mut data.data); + } + + xfer_destination.inflight = Some(chunk_range); + + let min_attestation = group.attestation_expiration_window(); + xfer_destination.cur_xfer_chunk_request = Some(PendingXferRequest { + id: PendingXferRequestId::XferChunkRequest { + new_last: chunk_last.to_pb(), + }, + message: Rc::new(ReplicaToReplicaMessage { + inner: Some(replica_to_replica_message::Inner::XferChunkRequest(XferChunkRequest { + data, + chunk_range: chunk_range.to_pb(), + min_attestation, + })), + }), + min_attestation: Some(min_attestation), + }); + Some(chunk_range) + } + + fn storage_split_to(storage: &mut BTreeMap>, maybe_split_key: Option<&PartitionKey>) + -> impl DoubleEndedIterator)> + ExactSizeIterator { + let new_map = if let Some(split_key) = maybe_split_key { + storage.split_off(split_key) + } else { + Default::default() + }; + std::mem::replace(storage, new_map).into_iter() + } + + fn perform_apply_chunk_transaction(&mut self, txn: ApplyChunkTransaction, group: &mut ReplicaGroupState) -> EnclaveApplyChunkTransaction { + if let XferState::DestinationPartition(xfer_source) = &mut self.xfer_state { + let request = &txn.xfer_chunk_request; + let entry_len = XferBackupEntry::encoded_len().to_usize(); + let entries_len = request.data.data.len() / entry_len; + info!("received xfer chunk {} length {}", &request.chunk_range, entries_len); + + let new_first = if let Some(range) = &self.range { + range.first() + } else { + xfer_source.desired_range.first() + }; + + let chunk_range = match PartitionKeyRange::try_from_pb(&request.chunk_range) { + Ok(chunk_range) => { + if xfer_source.desired_range.contains_range(&chunk_range) { + if !self.range.map(|range| range.overlaps_range(&chunk_range)).unwrap_or(false) { + chunk_range + } else { + warn!("dropping old xfer chunk {} current range {}", + &chunk_range, OptionDisplay(self.range)); + return Default::default(); + } + } else { + error!("dropping xfer chunk {} not in desired range {}", + &chunk_range, &xfer_source.desired_range); + return Default::default(); + } + } + Err(()) => { + error!("dropping xfer chunk with invalid range {}", &request.chunk_range); + return Default::default(); + } + }; + + let new_range = match PartitionKeyRange::new(*new_first, *chunk_range.last()) { + Ok(new_range) => new_range, + Err(()) => { + warn!("dropping old xfer chunk {} < {}", chunk_range.last(), &new_first); + return Default::default(); + } + }; + let old_range = std::mem::replace(&mut self.range, Some(new_range)); + + let xfer_chunk_reply = PendingXferRequest { + id: PendingXferRequestId::XferChunkReply { + new_last: txn.xfer_chunk_reply.new_last.clone(), + }, + message: Rc::new(ReplicaToReplicaMessage { + inner: Some(replica_to_replica_message::Inner::XferChunkReply(txn.xfer_chunk_reply.clone())) + }), + min_attestation: None, + }; + let old_xfer_chunk_reply = mem::replace(&mut xfer_source.cur_xfer_chunk_reply, Some(xfer_chunk_reply)); + if let Some(old_xfer_chunk_reply) = old_xfer_chunk_reply { + xfer_source.received_reply(&old_xfer_chunk_reply.id); + } + + let AttestationParameters { unix_timestamp_seconds } = request.min_attestation; + let min_attestation_time = Duration::from_secs(unix_timestamp_seconds); + if min_attestation_time > group.get_attestation_time_now() { + group.set_attestation_time_now(min_attestation_time); + } + + for mut entry_data in request.data.data[..].chunks(entry_len) { + let entry = XferBackupEntry::decode(&mut entry_data); + if !old_range.map(|old_range| old_range.contains(&entry.id)).unwrap_or(false) { + self.storage.insert(entry.id, entry.entry); + } else { + error!("dropping transferred backup id {} within current range {}", + &entry.id, OptionDisplay(self.range)); + } + } + + if chunk_range.last() >= xfer_source.desired_range.last() { + info!("All transfer chunks applied"); + } + + drop(txn); + + let mut chunk_ids = Vec::with_capacity(entries_len); + for (backup_id, _) in self.storage.range(&chunk_range) { + chunk_ids.push(backup_id.to_pb()); + } + EnclaveApplyChunkTransaction { + chunk_range: Some(chunk_range.to_pb()), + chunk_ids, + } + } else { + warn!("dropping unexpected ApplyChunkTransaction {}", txn.xfer_chunk_reply.new_last); + Default::default() + } + } + + fn perform_pause_xfer_transaction(&mut self, txn: PauseXferTransaction) -> EnclavePauseXferTransaction { + let xfer_destination = match &mut self.xfer_state { + XferState::SourcePartition(xfer_destination) => xfer_destination, + _ => { + warn!("Tried to pause partitioning as a non-source replica!"); + send_untrusted_xfer_reply(txn.request_id, UntrustedXferReplyStatus::InvalidState); + return Default::default(); + } + }; + if !xfer_destination.paused { + info!("Pausing partitioning process at {}", OptionDisplay(self.range.as_ref().map(PartitionKeyRange::first))); + } + xfer_destination.paused = true; + + send_untrusted_xfer_reply(txn.request_id, UntrustedXferReplyStatus::Ok); + + EnclavePauseXferTransaction {} + } + + fn perform_resume_xfer_transaction(&mut self, txn: ResumeXferTransaction, group: &mut ReplicaGroupState) -> EnclaveResumeXferTransaction { + let xfer_destination = match &mut self.xfer_state { + XferState::SourcePartition(xfer_destination) => xfer_destination, + _ => { + warn!("Tried to resume partitioning as a non-source replica!"); + send_untrusted_xfer_reply(txn.request_id, UntrustedXferReplyStatus::InvalidState); + return Default::default(); + } + }; + info!("Resuming partitioning process with next chunk {}", &txn.chunk_last); + xfer_destination.paused = false; + let chunk_range = self.remove_chunk(&txn.chunk_last, group); + + send_untrusted_xfer_reply(txn.request_id, UntrustedXferReplyStatus::Ok); + + EnclaveResumeXferTransaction { + chunk_range: chunk_range.as_ref().map(PartitionKeyRange::to_pb), + } + } + + fn perform_finish_xfer_transaction(&mut self, txn: FinishXferTransaction) -> EnclaveFinishXferTransaction { + let status = match &mut self.xfer_state { + XferState::DestinationPartition(xfer_source) => { + let xfer_done = if let Some(range) = &self.range { + range.contains_range(&xfer_source.desired_range) + } else { + false + }; + if xfer_done || txn.force { + info!("Finishing partitioning process"); + // XXX Disconnect source nodes + self.xfer_state = XferState::None; + UntrustedXferReplyStatus::Ok + } else { + warn!("tried to finish in-progress partitioning process!"); + UntrustedXferReplyStatus::InvalidState + } + } + XferState::SourcePartition(xfer_destination) => { + let xfer_done = if let Some(range) = &self.range { + !xfer_destination.full_range.contains(range.first()) + } else { + true + }; + if xfer_done || txn.force { + info!("Finishing partitioning process"); + self.xfer_state = XferState::None; + UntrustedXferReplyStatus::Ok + } else { + warn!("tried to finish in-progress partitioning process!"); + UntrustedXferReplyStatus::InvalidState + } + } + XferState::None => UntrustedXferReplyStatus::InvalidState, + }; + + send_untrusted_xfer_reply(txn.request_id, status); + EnclaveFinishXferTransaction {} + } + + fn perform_set_time_transaction(&mut self, txn: SetTimeTransaction, group: &mut ReplicaGroupState) -> EnclaveSetTimeTransaction { + if !group.set_attestation_time_now(Duration::from_secs(txn.now_secs)) { + warn!("tried to set attestation time backward from {} to {}", + group.get_attestation_time_now().as_secs(), txn.now_secs); + Default::default() + } else { + EnclaveSetTimeTransaction { + now_secs: Some(txn.now_secs), + } + } + } +} + +// +// XferState impls +// + +impl XferState { + pub fn remote_group_mut(&mut self) -> Option<&mut RemoteGroupState> { + match self { + XferState::DestinationPartition(xfer_source) => Some(&mut xfer_source.remote_group), + XferState::SourcePartition(xfer_destination) => Some(&mut xfer_destination.remote_group), + XferState::None => None, + } + } +} + +// +// XferSource impls +// + +impl XferSource { + pub fn new(remote_group: RemoteGroupState, + desired_range: PartitionKeyRange) + -> Self + { + Self { + remote_group, + desired_range, + cur_xfer_chunk_reply: None, + } + } + + pub fn remote_group(&self) -> &RemoteGroupState { + &self.remote_group + } + + pub fn remote_group_mut(&mut self) -> &mut RemoteGroupState { + &mut self.remote_group + } + + pub fn desired_range(&self) -> &PartitionKeyRange { + &self.desired_range + } + + pub fn received_reply(&mut self, request_id: &PendingXferRequestId) { + if let Some(pending_request) = self.remote_group.handle_reply(request_id) { + match &pending_request.id { + PendingXferRequestId::XferRequest => (), + PendingXferRequestId::XferReply => (), + PendingXferRequestId::XferChunkReply { .. } => { + if let Some(cur_xfer_chunk_reply) = &self.cur_xfer_chunk_reply { + if cur_xfer_chunk_reply.id == pending_request.id { + self.cur_xfer_chunk_reply = None; + } + } + } + PendingXferRequestId::XferChunkRequest { .. } => (), + } + } + } +} + +// +// XferDestination impls +// + +impl XferDestination { + pub fn remote_group(&self) -> &RemoteGroupState { + &self.remote_group + } + + pub fn remote_group_mut(&mut self) -> &mut RemoteGroupState { + &mut self.remote_group + } + + pub fn full_range(&self) -> &PartitionKeyRange { + &self.full_range + } + + pub fn chunk_size(&self) -> u32 { + self.chunk_size + } + + pub fn received_reply(&mut self, request_id: &PendingXferRequestId) { + if let Some(pending_request) = self.remote_group.handle_reply(request_id) { + match &pending_request.id { + PendingXferRequestId::XferRequest => (), + PendingXferRequestId::XferReply => { + self.cur_xfer_reply = None; + } + PendingXferRequestId::XferChunkReply { .. } => (), + PendingXferRequestId::XferChunkRequest { .. } => { + if let Some(cur_xfer_chunk_request) = &self.cur_xfer_chunk_request { + if cur_xfer_chunk_request.id == pending_request.id { + self.cur_xfer_chunk_request = None; + } + } + } + } + } + } +} + +// +// PendingXferRequest impls +// + +impl RemoteGroupPendingRequest for PendingXferRequest { + type RequestId = PendingXferRequestId; + type Message = ReplicaToReplicaMessage; + fn request_id(&self) -> &Self::RequestId { + &self.id + } + fn message(&self) -> Rc { + Rc::clone(&self.message) + } + fn min_attestation(&self) -> Option { + self.min_attestation + } +} + +// +// RequestNonce impls +// + +impl RequestNonce { + pub fn from_combined(mut combined: [u8; 32]) -> Self { + let (creation_nonce, current_nonce) = Self::split_mut(&mut combined); + Self { + creation_nonce: *creation_nonce, + current_nonce: *current_nonce, + } + } + pub fn to_combined(&self) -> [u8; 32] { + let mut combined = [0; 32]; + + let (combined_creation_nonce, combined_current_nonce) = Self::split_mut(&mut combined); + + *combined_creation_nonce = self.creation_nonce; + *combined_current_nonce = self.current_nonce; + + combined + } + + fn split_mut(combined: &mut [u8; 32]) -> (&mut [u8; 16], &mut [u8; 16]) { + let (creation_nonce, current_nonce) = combined.split_at_mut(16); + let creation_nonce: &mut [u8; 16] = creation_nonce.try_into().unwrap_or_else(|_| static_unreachable!()); + let current_nonce: &mut [u8; 16] = current_nonce.try_into().unwrap_or_else(|_| static_unreachable!()); + (creation_nonce, current_nonce) + } +} + +// +// BackupEntry impls +// + +impl BackupEntry { + fn encoded_len() -> u32 { + 32 + 32 + 32 + 2 + } + fn encode(&self, buf: &mut B) { + buf.put_u16_le(self.tries.map(u16::from).unwrap_or(0)); + buf.put_slice(self.pin.get()); + buf.put_slice(&self.creation_nonce); + buf.put_slice(&self.current_nonce); + buf.put_slice(self.data.get()); + } + fn decode(buf: &mut B) -> Box { + let tries = NonZeroU16::new(buf.get_u16_le()); + let mut value = Box::new(Self { + tries, + pin: Default::default(), + creation_nonce: Default::default(), + current_nonce: Default::default(), + data: Default::default(), + }); + buf.copy_to_slice(value.pin.get_mut()); + buf.copy_to_slice(&mut value.creation_nonce); + buf.copy_to_slice(&mut value.current_nonce); + buf.copy_to_slice(value.data.get_mut()); + value + } + + fn request_nonce(&self) -> RequestNonce { + RequestNonce { + current_nonce: self.current_nonce, + creation_nonce: self.creation_nonce, + } + } +} + +// +// XferBackupEntry impls +// + +impl XferBackupEntry { + fn encoded_len() -> u32 { + BackupId::valid_len() + BackupEntry::encoded_len() + } + fn encode(&self, buf: &mut B) { + self.entry.encode(buf); + buf.put_slice(&self.id[..]); + } + fn decode(buf: &mut B) -> Self { + let entry = BackupEntry::decode(buf); + let mut id = [0; 32]; + buf.copy_to_slice(&mut id); + Self { id: PartitionKey::new(id), entry } + } +} + +// +// utils +// + +fn send_untrusted_xfer_reply(request_id: u64, status: UntrustedXferReplyStatus) { + kbupd_send(EnclaveMessage { + inner: Some(enclave_message::Inner::UntrustedXferReply(UntrustedXferReply { + request_id, + status: status.into(), + })), + }); +} diff --git a/enclave/kbupd_enclave/src/service/replica/partition_key_range.rs b/enclave/kbupd_enclave/src/service/replica/partition_key_range.rs new file mode 100644 index 0000000..e2d1187 --- /dev/null +++ b/enclave/kbupd_enclave/src/service/replica/partition_key_range.rs @@ -0,0 +1,564 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fmt; +use std::ops::*; + +use crate::protobufs::kbupd::*; +use crate::util::*; + +#[derive(Copy, Clone, Eq, PartialOrd, Ord)] +pub struct PartitionKey([u8; 32]); + +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct PartitionKeyRange { + first: PartitionKey, + last: PartitionKey, +} + +// +// PartittionKeyRange impls +// + +impl PartitionKeyRange { + pub fn new(first: PartitionKey, last: PartitionKey) -> Result { + if first <= last { + Ok(Self { first, last }) + } else { + Err(()) + } + } + + pub fn new_unbounded() -> Self { + Self { + first: PartitionKey::new([0x00; 32]), + last: PartitionKey::new([0xFF; 32]), + } + } + + pub fn try_from_pb(pb: &PartitionKeyRangePb) -> Result { + Self::from_ids(&pb.first, &pb.last) + } + + fn from_ids(first: &BackupId, last: &BackupId) -> Result { + Self::new(PartitionKey::try_from_pb(first)?, PartitionKey::try_from_pb(last)?) + } + + pub fn to_pb(&self) -> PartitionKeyRangePb { + PartitionKeyRangePb { + first: BackupId { id: self.first.to_vec() }, + last: BackupId { id: self.last.to_vec() }, + } + } + + pub fn first(&self) -> &PartitionKey { + &self.first + } + + pub fn last(&self) -> &PartitionKey { + &self.last + } + + pub fn contains(&self, key: &[u8; 32]) -> bool { + (key >= &self.first && + key <= &self.last) + } + + pub fn contains_id(&self, key: &BackupId) -> bool { + let mut id: [u8; 32] = Default::default(); + if key.id.len() == id.len() { + id.copy_from_slice(&key.id); + self.contains(&id) + } else { + false + } + } + + pub fn contains_range(&self, other: &Self) -> bool { + self.contains(&other.first) && self.contains(&other.last) + } + + pub fn overlaps_range(&self, other: &Self) -> bool { + (self.contains(&other.first) || self.contains(&other.last) || + other.contains(&self.first) || other.contains(&self.last)) + } + + pub fn split_off_inclusive(&mut self, new_last: &PartitionKey) -> Result, ()> { + if self.contains(new_last) { + if let Some(other_first) = new_last.checked_add(1) { + let other_last = std::mem::replace(&mut self.last, *new_last); + Ok(Self::new(other_first, other_last).ok()) + } else { + Ok(None) + } + } else { + Err(()) + } + } +} + +impl RangeBounds for &PartitionKeyRange { + fn start_bound(&self) -> Bound<&PartitionKey> { + Bound::Included(&self.first) + } + fn end_bound(&self) -> Bound<&PartitionKey> { + Bound::Included(&self.last) + } +} + +impl fmt::Debug for PartitionKeyRange { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Display::fmt(self, fmt) + } +} +impl fmt::Display for PartitionKeyRange { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let Self { first, last } = self; + write!(fmt, "{}-{}", first, last) + } +} + + +// +// PartitionKey impls +// + +impl PartitionKey { + pub fn new(value: [u8; 32]) -> Self { + Self(value) + } + + pub fn try_from_pb(backup_id: &BackupId) -> Result { + let mut value = [0u8; 32]; + if backup_id.id.len() == value.len() { + value.copy_from_slice(&backup_id.id); + Ok(Self(value)) + } else { + Err(()) + } + } + + pub fn to_pb(&self) -> BackupId { + BackupId { + id: self.0.to_vec(), + } + } + + #[allow(clippy::indexing_slicing, clippy::integer_arithmetic)] + pub fn checked_sub(&self, rhs: u8) -> Option { + let mut ret = [0x00; 32]; + let mut carry = rhs; + for (i, b) in self.0.iter().rev().enumerate() { + let new_b = b.overflowing_sub(carry); + carry = if new_b.1 { 1 } else { 0 }; + ret[31 - i] = new_b.0; + } + if carry == 0 { + Some(Self(ret)) + } else { + None + } + } + #[allow(clippy::indexing_slicing, clippy::integer_arithmetic)] + pub fn checked_add(&self, rhs: u8) -> Option { + let mut ret = [0x00; 32]; + let mut carry = rhs; + for (i, b) in self.0.iter().rev().enumerate() { + let new_b = b.overflowing_add(carry); + carry = if new_b.1 { 1 } else { 0 }; + ret[31 - i] = new_b.0; + } + if carry == 0 { + Some(Self(ret)) + } else { + None + } + } +} + +impl Deref for PartitionKey { + type Target = [u8; 32]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PartialEq for PartitionKey +where T: AsRef<[u8]> +{ + fn eq(&self, other: &T) -> bool { + self.0 == other.as_ref() + } +} + +impl AsRef<[u8]> for PartitionKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl fmt::Debug for PartitionKey { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Display::fmt(self, fmt) + } +} + +impl fmt::Display for PartitionKey { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let Self(key) = self; + write!(fmt, "{}", ToHex(key)) + } +} + +// +// tests +// + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn valid_full_range() { + PartitionKeyRange::try_from_pb( + &PartitionKeyRange::from_ids(&BackupId { id: vec![0x00; 32] }, + &BackupId { id: vec![0xFF; 32] }) + .unwrap() + .to_pb() + ).unwrap(); + } + #[test] + fn valid_empty_range() { + PartitionKeyRange::try_from_pb( + &PartitionKeyRange::from_ids(&BackupId { id: vec![0x00; 32] }, + &BackupId { id: vec![0x00; 32] }) + .unwrap() + .to_pb() + ).unwrap(); + } + + #[test] + fn invalid_inverted_range() { + PartitionKeyRange::from_ids(&BackupId { id: vec![0xFF; 32] }, + &BackupId { id: vec![0x00; 32] }) + .unwrap_err(); + } + + #[test] + fn invalid_range_empty_first() { + PartitionKeyRange::from_ids(&BackupId { id: vec![0x00; 0] }, + &BackupId { id: vec![0xFF; 32] }) + .unwrap_err(); + } + + #[test] + fn invalid_range_empty_last() { + PartitionKeyRange::from_ids(&BackupId { id: vec![0x00; 32] }, + &BackupId { id: vec![0xFF; 0] }) + .unwrap_err(); + } + + #[test] + fn invalid_range_short_first() { + PartitionKeyRange::from_ids(&BackupId { id: vec![0x00; 31] }, + &BackupId { id: vec![0xFF; 32] }) + .unwrap_err(); + } + + #[test] + fn invalid_range_short_last() { + PartitionKeyRange::from_ids(&BackupId { id: vec![0x00; 31] }, + &BackupId { id: vec![0xFF; 32] }) + .unwrap_err(); + } + + #[test] + fn invalid_range_long_first() { + PartitionKeyRange::from_ids(&BackupId { id: vec![0x00; 33] }, + &BackupId { id: vec![0xFF; 32] }) + .unwrap_err(); + } + + #[test] + fn invalid_range_long_last() { + PartitionKeyRange::from_ids(&BackupId { id: vec![0x00; 32] }, + &BackupId { id: vec![0xFF; 33] }) + .unwrap_err(); + } + + #[test] + fn contains_id_valid() { + let range = PartitionKeyRange::new_unbounded(); + assert!(range.contains_id(&BackupId { id: vec![0x00; 32] })); + assert!(range.contains_id(&BackupId { id: vec![0xFF; 32] })); + assert!(range.contains_id(&BackupId { id: vec![0x80; 32] })); + + let range = PartitionKeyRange::new(PartitionKey::new([0x00; 32]), PartitionKey::new([0x80; 32])).unwrap(); + assert!(range.contains_id(&BackupId { id: vec![0x00; 32] })); + assert!(range.contains_id(&PartitionKey::new([0x80; 32]).checked_sub(1).unwrap().to_pb())); + assert!(range.contains_id(&BackupId { id: vec![0x80; 32] })); + assert!(!range.contains_id(&PartitionKey::new([0x80; 32]).checked_add(1).unwrap().to_pb())); + assert!(!range.contains_id(&BackupId { id: vec![0xFF; 32] })); + + let range = PartitionKeyRange::new(PartitionKey::new([0xBE; 32]), PartitionKey::new([0xEF; 32])).unwrap(); + assert!(!range.contains_id(&BackupId { id: vec![0x00; 32] })); + assert!(!range.contains_id(&PartitionKey::new([0xBE; 32]).checked_sub(1).unwrap().to_pb())); + assert!(range.contains_id(&BackupId { id: vec![0xBE; 32] })); + assert!(range.contains_id(&PartitionKey::new([0xBE; 32]).checked_add(1).unwrap().to_pb())); + assert!(range.contains_id(&PartitionKey::new([0xEF; 32]).checked_sub(1).unwrap().to_pb())); + assert!(range.contains_id(&BackupId { id: vec![0xEF; 32] })); + assert!(!range.contains_id(&PartitionKey::new([0xEF; 32]).checked_add(1).unwrap().to_pb())); + assert!(!range.contains_id(&BackupId { id: vec![0xFF; 32] })); + } + + #[test] + fn contains_id_invalid() { + assert!(!PartitionKeyRange::new_unbounded().contains_id(&BackupId { id: vec![] })); + assert!(!PartitionKeyRange::new_unbounded().contains_id(&BackupId { id: vec![0x00; 31] })); + assert!(!PartitionKeyRange::new_unbounded().contains_id(&BackupId { id: vec![0x00; 33] })); + } + + macro_rules! key { + ([$value:expr] + $addend:expr) => ({ + PartitionKey::new([$value; 32]).checked_add($addend) + .unwrap_or_else(|| panic!("test key overflow")) + }); + ([$value:expr] - $subtrahend:expr) => ({ + PartitionKey::new([$value; 32]).checked_sub($subtrahend) + .unwrap_or_else(|| panic!("test key underflow")) + }); + ([$value:expr]) => ({ + PartitionKey::new([$value; 32]) + }); + } + + macro_rules! range { + ([$value:expr], $($second:tt)*) => (range!({key!([$value])}, $($second)*)); + ([$value:expr] + $addend:expr, $($second:tt)*) => (range!({key!([$value] + $addend)}, $($second)*)); + ([$value:expr] - $subtrahend:expr, $($second:tt)*) => (range!({key!([$value] - $subtrahend)}, $($second)*)); + + ($first:block, [$value:expr]) => (range!($first, {key!([$value])})); + ($first:block, [$value:expr] + $addend:expr) => (range!($first, {key!([$value] + $addend)})); + ($first:block, [$value:expr] - $subtrahend:expr) => (range!($first, {key!([$value] - $subtrahend)})); + + ($first:block, $last:block) => ({ + PartitionKeyRange::new($first, $last).unwrap_or_else(|()| panic!("invalid test range")) + }); + () => ({ + PartitionKeyRange::new_unbounded() + }); + } + + #[test] + fn overlaps_range() { + let range = range!(); + assert!(range.overlaps_range(&range!())); + assert!(range.overlaps_range(&range!([0x00], [0x00]))); + assert!(range.overlaps_range(&range!([0x00], [0xBE]))); + assert!(range.overlaps_range(&range!([0xBE], [0xEF]))); + assert!(range.overlaps_range(&range!([0xEF], [0xFF]))); + assert!(range.overlaps_range(&range!([0xFF], [0xFF]))); + + let range = range!([0x00], [0xBE]); + assert!( range.overlaps_range(&range!())); + assert!( range.overlaps_range(&range!([0x00], [0x00]))); + assert!( range.overlaps_range(&range!([0x00], [0xBE]-1))); + assert!( range.overlaps_range(&range!([0x00], [0xBE]))); + assert!( range.overlaps_range(&range!([0x00], [0xFF]))); + assert!( range.overlaps_range(&range!([0xBA], [0xBA]))); + assert!( range.overlaps_range(&range!([0xBA], [0xBE]))); + assert!( range.overlaps_range(&range!([0xBA], [0xBE]+1))); + assert!( range.overlaps_range(&range!([0xBE], [0xBE]))); + assert!( range.overlaps_range(&range!([0xBE], [0xBE]+1))); + assert!(!range.overlaps_range(&range!([0xBE]+1, [0xEF]))); + assert!(!range.overlaps_range(&range!([0xEF], [0xFF]))); + assert!(!range.overlaps_range(&range!([0xFF], [0xFF]))); + + let range = range!([0xBE], [0xEF]); + assert!( range.overlaps_range(&range!())); + assert!(!range.overlaps_range(&range!([0x00], [0x00]))); + assert!(!range.overlaps_range(&range!([0x00], [0xBE]-1))); + assert!( range.overlaps_range(&range!([0x00], [0xBE]))); + assert!( range.overlaps_range(&range!([0x00], [0xBF]))); + assert!( range.overlaps_range(&range!([0x00], [0xEF]))); + assert!( range.overlaps_range(&range!([0x00], [0xEF]+1))); + assert!( range.overlaps_range(&range!([0x00], [0xFF]))); + assert!( range.overlaps_range(&range!([0xBE], [0xEF]))); + assert!( range.overlaps_range(&range!([0xBE], [0xEF]+1))); + assert!( range.overlaps_range(&range!([0xBE]+1, [0xEF]-1))); + assert!( range.overlaps_range(&range!([0xBE]+1, [0xEF]))); + assert!( range.overlaps_range(&range!([0xBE]+1, [0xEF]+1))); + assert!( range.overlaps_range(&range!([0xEF], [0xEF]))); + assert!( range.overlaps_range(&range!([0xEF], [0xEF]+1))); + assert!(!range.overlaps_range(&range!([0xEF]+1, [0xEF]+1))); + assert!(!range.overlaps_range(&range!([0xEF]+1, [0xFF]))); + + let range = range!([0xEF], [0xFF]); + assert!(!range.overlaps_range(&range!([0x00], [0x00]))); + assert!(!range.overlaps_range(&range!([0x00], [0xEF]-1))); + assert!( range.overlaps_range(&range!([0x00], [0xEF]))); + assert!( range.overlaps_range(&range!([0x00], [0xEF]+1))); + assert!( range.overlaps_range(&range!([0x00], [0xFF]))); + assert!( range.overlaps_range(&range!([0xEF], [0xEF]))); + assert!( range.overlaps_range(&range!([0xEF], [0xEF]+1))); + assert!( range.overlaps_range(&range!([0xEF], [0xFF]))); + assert!( range.overlaps_range(&range!([0xEF]+1, [0xFF]-1))); + assert!( range.overlaps_range(&range!([0xEF]+1, [0xFF]))); + assert!( range.overlaps_range(&range!([0xFF], [0xFF]))); + } + + #[test] + fn split_off_first() { + let mut range = PartitionKeyRange::new_unbounded(); + let split_off = range.split_off_inclusive(&PartitionKey::new([0x00; 32])).unwrap().unwrap(); + assert_eq!(range.first(), &[0x00; 32]); + assert_eq!(range.last(), &[0x00; 32]); + assert_eq!(split_off.first(), &range.last().checked_add(1).unwrap()); + assert_eq!(split_off.last(), &[0xff; 32]); + + let mut range = PartitionKeyRange::new(PartitionKey::new([0xbe; 32]), PartitionKey::new([0xef; 32])).unwrap(); + let split_off = range.split_off_inclusive(&PartitionKey::new([0xbe; 32])).unwrap().unwrap(); + assert_eq!(range.first(), &[0xbe; 32]); + assert_eq!(range.last(), &[0xbe; 32]); + assert_eq!(split_off.first(), &range.last().checked_add(1).unwrap()); + assert_eq!(split_off.last(), &[0xef; 32]); + } + + #[test] + fn split_off_mid() { + let mut range = PartitionKeyRange::new_unbounded(); + let split_off = range.split_off_inclusive(&PartitionKey::new([0x80; 32])).unwrap().unwrap(); + assert_eq!(range.first(), &[0x00; 32]); + assert_eq!(range.last(), &[0x80; 32]); + assert_eq!(split_off.first(), &range.last().checked_add(1).unwrap()); + assert_eq!(split_off.last(), &[0xff; 32]); + + let mut range = PartitionKeyRange::new(PartitionKey::new([0xbe; 32]), PartitionKey::new([0xef; 32])).unwrap(); + let split_off = range.split_off_inclusive(&PartitionKey::new([0xdd; 32])).unwrap().unwrap(); + assert_eq!(range.first(), &[0xbe; 32]); + assert_eq!(range.last(), &[0xdd; 32]); + assert_eq!(split_off.first(), &range.last().checked_add(1).unwrap()); + assert_eq!(split_off.last(), &[0xef; 32]); + } + + #[test] + fn split_off_last() { + let mut range = PartitionKeyRange::new_unbounded(); + let split_off = range.split_off_inclusive(&PartitionKey::new([0xff; 32])).unwrap(); + assert_eq!(range.first(), &[0x00; 32]); + assert_eq!(range.last(), &[0xff; 32]); + assert!(split_off.is_none()); + + let mut range = PartitionKeyRange::new(PartitionKey::new([0xbe; 32]), PartitionKey::new([0xef; 32])).unwrap(); + let split_off = range.split_off_inclusive(&PartitionKey::new([0xef; 32])).unwrap(); + assert_eq!(range.first(), &[0xbe; 32]); + assert_eq!(range.last(), &[0xef; 32]); + assert!(split_off.is_none()); + } + + #[test] + fn split_off_small() { + let mut range = PartitionKeyRange::new(PartitionKey::new([0x00; 32]), PartitionKey::new([0x00; 32])).unwrap(); + let split_off = range.split_off_inclusive(&PartitionKey::new([0x00; 32])).unwrap(); + assert_eq!(range.first(), &[0x00; 32]); + assert_eq!(range.last(), &[0x00; 32]); + assert!(split_off.is_none()); + + let mut range = PartitionKeyRange::new(PartitionKey::new([0xbe; 32]), PartitionKey::new([0xbe; 32])).unwrap(); + let split_off = range.split_off_inclusive(&PartitionKey::new([0xbe; 32])).unwrap(); + assert_eq!(range.first(), &[0xbe; 32]); + assert_eq!(range.last(), &[0xbe; 32]); + assert!(split_off.is_none()); + + let mut range = PartitionKeyRange::new(PartitionKey::new([0xff; 32]), PartitionKey::new([0xff; 32])).unwrap(); + let split_off = range.split_off_inclusive(&PartitionKey::new([0xff; 32])).unwrap(); + assert_eq!(range.first(), &[0xff; 32]); + assert_eq!(range.last(), &[0xff; 32]); + assert!(split_off.is_none()); + } + + #[test] + fn split_off_err() { + let mut range = PartitionKeyRange::new(PartitionKey::new([0x00; 32]), PartitionKey::new([0x00; 32])).unwrap(); + range.split_off_inclusive(&range.last().checked_add(1).unwrap()).unwrap_err(); + range.split_off_inclusive(&PartitionKey::new([0xff; 32])).unwrap_err(); + + let mut range = PartitionKeyRange::new(PartitionKey::new([0x00; 32]), PartitionKey::new([0xbe; 32])).unwrap(); + range.split_off_inclusive(&range.last().checked_add(1).unwrap()).unwrap_err(); + range.split_off_inclusive(&PartitionKey::new([0xbf; 32])).unwrap_err(); + range.split_off_inclusive(&PartitionKey::new([0xff; 32])).unwrap_err(); + + let mut range = PartitionKeyRange::new(PartitionKey::new([0xbe; 32]), PartitionKey::new([0xbe; 32])).unwrap(); + range.split_off_inclusive(&PartitionKey::new([0x00; 32])).unwrap_err(); + range.split_off_inclusive(&PartitionKey::new([0xba; 32])).unwrap_err(); + range.split_off_inclusive(&range.first().checked_sub(1).unwrap()).unwrap_err(); + range.split_off_inclusive(&range.last().checked_add(1).unwrap()).unwrap_err(); + range.split_off_inclusive(&PartitionKey::new([0xef; 32])).unwrap_err(); + range.split_off_inclusive(&PartitionKey::new([0xff; 32])).unwrap_err(); + + let mut range = PartitionKeyRange::new(PartitionKey::new([0xbf; 32]), PartitionKey::new([0xff; 32])).unwrap(); + range.split_off_inclusive(&PartitionKey::new([0x00; 32])).unwrap_err(); + range.split_off_inclusive(&PartitionKey::new([0xbe; 32])).unwrap_err(); + range.split_off_inclusive(&range.first().checked_sub(1).unwrap()).unwrap_err(); + + let mut range = PartitionKeyRange::new(PartitionKey::new([0xff; 32]), PartitionKey::new([0xff; 32])).unwrap(); + range.split_off_inclusive(&PartitionKey::new([0x00; 32])).unwrap_err(); + range.split_off_inclusive(&PartitionKey::new([0xfe; 32])).unwrap_err(); + } + + #[test] + fn checked_add() { + for index in 0..32 { + for index_value in 0x00..0xfe { + let mut value = [0x00; 32]; + for byte in value[index + 1..32].iter_mut() { + *byte = 0xff; + } + value[index] = index_value; + let mut addend = [0x00; 32]; + addend[index] = index_value + 1; + assert_eq!(PartitionKey::new(value).checked_add(1).unwrap(), &addend); + } + } + } + + #[test] + fn checked_sub() { + for index in 0..32 { + for index_value in 0x01..0xff { + let mut value = [0x00; 32]; + value[index] = index_value; + let mut addend = [0x00; 32]; + addend[index] = index_value - 1; + for byte in addend[index + 1..32].iter_mut() { + *byte = 0xff; + } + assert_eq!(PartitionKey::new(value).checked_sub(1).unwrap(), &addend); + } + } + } + + #[test] + fn checked_add_overflow() { + assert_eq!(PartitionKey::new([0xff; 32]).checked_add(1), None); + } + + #[test] + fn checked_sub_overflow() { + assert_eq!(PartitionKey::new([0x00; 32]).checked_sub(1), None); + } +} diff --git a/enclave/kbupd_enclave/src/service/replica/replica_group.rs b/enclave/kbupd_enclave/src/service/replica/replica_group.rs new file mode 100644 index 0000000..afb3d89 --- /dev/null +++ b/enclave/kbupd_enclave/src/service/replica/replica_group.rs @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::prelude::*; + +use std::fmt; +use std::rc::*; +use std::time::*; + +use sgxsd_ffi::{RdRand}; + +use crate::protobufs::kbupd_enclave::*; +use crate::raft::*; +use crate::remote::*; +use crate::storage::*; +use crate::util::{ListDisplay}; + +use super::*; + +const ATTESTATION_EXPIRATION_WINDOW: Duration = Duration::from_secs(86400); + +pub(super) struct ReplicaGroupState { + pub raft: RaftState, + + remotes: Box<[RemoteReplicaState]>, + + attestation_time_ticks: u32, + request_quote_ticks: u32, + + attestation_time_now: Duration, +} + +pub(super) struct RemoteReplicaState { + pub sender: ReplicaRemoteSender, +} + +// +// ReplicaGroupState impls +// + +impl ReplicaGroupState { + pub fn new(raft: RaftState, + remotes: Box<[RemoteReplicaState]>) + -> Self + { + Self { + raft, + remotes, + attestation_time_ticks: 0, + request_quote_ticks: 0, + attestation_time_now: Duration::from_secs(0), + } + } + + pub fn id(&self) -> &RaftGroupId { + self.raft.group_id() + } + + pub fn set_config(&mut self, config: &EnclaveReplicaConfig) { + self.raft.log_mut().set_index_cache_size(config.raft_log_index_page_cache_size.to_usize()); + self.raft.set_election_timeout_ticks(config.election_timeout_ticks); + self.raft.set_heartbeat_timeout_ticks(config.heartbeat_timeout_ticks); + self.raft.set_replication_chunk_size(config.replication_chunk_size.to_usize()); + } + + pub fn get(&self, node_id: &NodeId) -> Option<&RemoteReplicaState> { + self.remotes.iter().find(|replica: &&RemoteReplicaState| replica.sender.id() == node_id) + } + + pub fn timer_tick(&mut self, + max_attestation_time_ticks: u32, + max_request_quote_ticks: u32, + now: Duration) + -> Option { + self.attestation_time_ticks = self.attestation_time_ticks.saturating_add(1); + self.request_quote_ticks = self.request_quote_ticks.saturating_add(1); + + if self.request_quote_ticks >= max_request_quote_ticks { + self.request_quote_ticks = Default::default(); + + for remote in &mut self.remotes[..] { + let _ignore = remote.sender.request_quote(EnclaveGetQuoteRequest {}); + } + } + + if self.attestation_time_ticks >= max_attestation_time_ticks { + if now > self.attestation_time_now { + match self.check_quorum_attestation_time(now) { + Ok(()) => { + self.attestation_time_ticks = 0; + Some(TransactionData { + inner: Some(transaction_data::Inner::SetTime(SetTimeTransaction { + now_secs: now.as_secs(), + })), + }) + } + Err(invalid) => { + if self.attestation_time_ticks.checked_rem(max_attestation_time_ticks) == Some(0) { + info!("not setting attestation time to {} with invalid attestations for {} of {} replicas: {}", + now.as_secs(), invalid.len(), self.remotes.iter().len(), ListDisplay(invalid)); + } + None + } + } + } else { + if now < self.attestation_time_now { + warn!("not setting attestation time backward from {} to {}", + self.attestation_time_now.as_secs(), now.as_secs()); + } + None + } + } else { + None + } + } + + pub fn is_authorized(&self, node_id: &NodeId) -> bool { + (self.get(node_id)) + .map(|replica: &RemoteReplicaState| replica.check_attestation_time(self.attestation_time_now)) + .unwrap_or(false) + } + + pub fn set_attestation_time_now(&mut self, now: Duration) -> bool { + if now > self.attestation_time_now { + for replica in self.remotes.iter() { + if !replica.check_attestation_time(now) { + if let Some(attestation) = replica.attestation() { + warn!("replica {} is now invalid at {}: {}", + replica.sender.id(), now.as_secs(), attestation); + } + } + } + info!("set attestation time from {} to {}", + self.attestation_time_now.as_secs(), now.as_secs()); + self.attestation_time_now = now; + true + } else { + now < self.attestation_time_now + } + } + + pub fn get_attestation_time_now(&self) -> Duration { + self.attestation_time_now + } + + pub fn attestation(&self) -> AttestationParameters { + AttestationParameters::new(self.attestation_time_now) + } + + pub fn attestation_expiration_window(&self) -> AttestationParameters { + let min_unix_timestamp = self.attestation_time_now.checked_sub(ATTESTATION_EXPIRATION_WINDOW).unwrap_or_default(); + AttestationParameters::new(min_unix_timestamp) + } + + fn check_quorum_attestation_time(&self, now: Duration) -> Result<(), Vec<&RemoteReplicaState>> { + let replicas_iter = self.remotes.iter(); + let replicas_len = replicas_iter.len(); + let invalid: Vec<_> = replicas_iter + .filter(|replica: &&RemoteReplicaState| !replica.check_attestation_time(now)) + .collect(); + if invalid.len() < quorum_size(replicas_len) { + if invalid.is_empty() { + info!("setting attestation time to {}", now.as_secs()); + } else { + warn!("setting attestation time to {} with invalid attestations for {} of {} replicas: {}", + now.as_secs(), invalid.len(), replicas_len, ListDisplay(invalid)); + } + Ok(()) + } else { + Err(invalid) + } + } + + pub fn send_raft_message(&self, sendable: SendableRaftMessage) { + match sendable { + SendableRaftMessage::Broadcast { message } => { + let r2r_message = Rc::new(ReplicaToReplicaMessage { + inner: Some(replica_to_replica_message::Inner::RaftMessage(message)), + }); + for replica in self.remotes.iter() { + if replica.check_attestation_time(self.attestation_time_now) { + let _ignore = replica.sender.send(Rc::clone(&r2r_message)); + } else { + verbose!("dropping broadcast message to {} with expired attestation {}", + replica.sender.id(), OptionDisplay(replica.attestation())); + } + } + } + SendableRaftMessage::Reply { message, from } => { + if let Some(replica) = self.get(&from) { + if replica.check_attestation_time(self.attestation_time_now) { + let r2r_message = Rc::new(ReplicaToReplicaMessage { + inner: Some(replica_to_replica_message::Inner::RaftMessage(message)), + }); + + let _ignore = replica.sender.send(r2r_message); + } else { + warn!("dropping message to {} with expired attestation {}: {}", + replica.sender.id(), OptionDisplay(replica.attestation()), message); + } + } + } + } + } +} + +// +// RemoteReplicaState impls +// + +impl RemoteReplicaState { + pub fn attestation(&self) -> Option { + self.sender.attestation() + } + + fn check_attestation_time(&self, now: Duration) -> bool { + let min_unix_timestamp = now.checked_sub(ATTESTATION_EXPIRATION_WINDOW).unwrap_or_default(); + let min_attestation = AttestationParameters::new(min_unix_timestamp); + self.attestation() >= Some(min_attestation) + } +} + +impl fmt::Display for RemoteReplicaState { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { sender } = self; + write!(fmt, "({}, {})", sender, OptionDisplay(sender.attestation())) + } +} diff --git a/enclave/kbupd_enclave/src/storage/mod.rs b/enclave/kbupd_enclave/src/storage/mod.rs new file mode 100644 index 0000000..1c09035 --- /dev/null +++ b/enclave/kbupd_enclave/src/storage/mod.rs @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod storage_array; +pub mod storage_data; +pub mod storage_page_cache; +pub mod raft_log; + +pub use self::storage_array::*; +pub use self::storage_data::*; +pub use self::storage_page_cache::*; +pub use self::raft_log::*; diff --git a/enclave/kbupd_enclave/src/storage/raft_log/mod.rs b/enclave/kbupd_enclave/src/storage/raft_log/mod.rs new file mode 100644 index 0000000..472d9b9 --- /dev/null +++ b/enclave/kbupd_enclave/src/storage/raft_log/mod.rs @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +mod raft_log_data; +mod raft_log_index; + +use sgx_ffi::util::{SecretValue}; + +use crate::prelude::*; +use crate::protobufs::raft::*; +use crate::raft::*; +use crate::util::*; + +use self::raft_log_data::*; +use self::raft_log_index::*; + +pub struct RaftLogStorage { + index: RaftLogIndex, + data: RaftLogData, + cancelled: Vec>>, +} + +// +// RaftLog impls +// + +impl RaftLogStorage { + pub fn new(data_size: usize, index_size: u32, index_cache_size: usize) -> Result { + Ok(Self { + index: RaftLogIndex::new(index_size, index_cache_size)?, + data: RaftLogData::new(data_size)?, + cancelled: Default::default(), + }) + } + pub fn take_cancelled(&mut self) -> Vec>> { + std::mem::replace(&mut self.cancelled, Default::default()) + } + pub fn data_len(&self) -> usize { + self.data.len() + } + pub fn set_index_cache_size(&mut self, index_cache_size: usize) { + self.index.set_cache_size(index_cache_size); + } +} +impl RaftLog for RaftLogStorage { + fn append(&mut self, log_entry: LogEntry) -> Result<(), RaftLogAppendError> { + let term = log_entry.term; + if !self.index.is_full() { + let data_entry = if !log_entry.data.is_empty() { + let data_entry = self.data.append(log_entry)?; + Some(data_entry) + } else { + None + }; + let index_entry = RaftLogIndexEntry { term, data: data_entry }; + if let Ok(()) = self.index.push_back(index_entry) { + Ok(()) + } else { + Err(RaftLogAppendError::InternalError) + } + } else { + Err(RaftLogAppendError::OutOfSpace { log_entry }) + } + } + fn pop_front(&mut self, truncate_to: LogIdx) -> Result<(), ()> { + if (!self.index.is_empty() && + self.index.prev_log_idx() < truncate_to) + { + if let Some(popped_entry) = self.index.pop_front() { + if let Some(popped_data_entry) = &popped_entry.data { + self.data.pop_front_to(popped_data_entry); + } + } + Ok(()) + } else { + Err(()) + } + } + fn cancel_from(&mut self, from_log_idx: LogIdx) -> Result { + let cancelled_data_entries = self.index.cancel_from(from_log_idx)?; + self.cancelled = self.data.cancel(cancelled_data_entries); + Ok(self.cancelled.len()) + } + fn get(&mut self, log_idx: LogIdx) -> Option { + let index_entry = self.index.get(log_idx)?; + let entry_data = if let Some(data_entry) = &index_entry.data { + self.data.read(data_entry)? + } else { + SecretValue::new(Vec::new()) + }; + Some(LogEntry::new(index_entry.term, entry_data)) + } + fn get_len(&mut self, log_idx: LogIdx) -> Option { + let index_entry = self.index.get(log_idx)?; + if let Some(data_entry) = &index_entry.data { + Some(data_entry.length.to_usize()) + } else { + Some(0) + } + } + fn get_term(&mut self, log_idx: LogIdx) -> Option { + if log_idx == self.index.prev_log_idx() { + self.index.prev_log_term() + } else { + self.index.get(log_idx).map(|entry| entry.term) + } + } + fn prev_idx(&self) -> LogIdx { + self.index.prev_log_idx() + } + fn last_idx(&self) -> LogIdx { + self.index.last_log_idx() + } + fn last_term(&self) -> TermId { + self.index.last_log_term() + } +} diff --git a/enclave/kbupd_enclave/src/storage/raft_log/raft_log_data.rs b/enclave/kbupd_enclave/src/storage/raft_log/raft_log_data.rs new file mode 100644 index 0000000..462e108 --- /dev/null +++ b/enclave/kbupd_enclave/src/storage/raft_log/raft_log_data.rs @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::prelude::*; + +use num_traits::{ToPrimitive}; +use sgx_ffi::util::{SecretValue}; + +use crate::protobufs::raft::LogEntry; +use crate::raft::*; +use crate::storage::storage_data::*; +use crate::util::*; + +pub struct RaftLogData { + storage: StorageData, + + head: usize, + tail: usize, +} + +#[derive(Clone, Copy)] +pub struct RaftLogDataEntry { + pub nonce: StorageDataNonce, + pub offset: usize, + pub length: u32, +} + +impl RaftLogData { + pub fn new(data_size: usize) -> Result { + Ok(Self { + storage: StorageData::new(data_size)?, + head: Default::default(), + tail: Default::default(), + }) + } + + pub fn len(&self) -> usize { + if let Some(len) = self.head.checked_sub(self.tail) { + len + } else { + self.storage.len().saturating_sub(self.tail) + .saturating_add(self.head) + } + } + + pub fn append(&mut self, log_entry: LogEntry) -> Result { + let append_len = log_entry.data.len().saturating_add(StorageData::tag_len().into()); + let append_len = match append_len.to_u32() { + Some(append_len) if append_len.to_usize() < self.storage.len() => append_len, + Some(_) | None => { + error!("transaction too large at {} bytes (have {} bytes of storage)", log_entry.data.len(), self.storage.len()); + return Err(RaftLogAppendError::TooLarge { size: append_len }); + } + }; + let mut offset = self.head; + let mut new_head = offset.saturating_add(append_len.to_usize()); + if new_head >= self.storage.len() { + if self.tail > 0 { + offset = 0; + new_head = append_len.to_usize(); + } else { + return Err(RaftLogAppendError::OutOfSpace { log_entry }); + } + } + + if offset < self.tail && new_head >= self.tail { + Err(RaftLogAppendError::OutOfSpace { log_entry }) + } else { + self.head = new_head; + + match self.storage.write(offset, log_entry.into_data()) { + Ok(nonce) => { + Ok(RaftLogDataEntry { nonce, offset, length: append_len }) + } + Err(()) => { + error!("wrote out of bounds to raft log at {} len {}", offset, append_len); + Err(RaftLogAppendError::InternalError) + } + } + } + } + + pub fn cancel(&mut self, cancelled_data_entries: Vec) -> Vec>> { + if let Some(cancel_from_data_entry) = cancelled_data_entries.get(0) { + self.head = cancel_from_data_entry.offset; + } + + let mut cancelled_data = Vec::new(); + for cancelled_data_entry in cancelled_data_entries { + if let Some(data) = self.read(&cancelled_data_entry) { + cancelled_data.push(data); + } + } + cancelled_data + } + + pub fn pop_front_to(&mut self, popped_entry: &RaftLogDataEntry) { + let tail = popped_entry.offset.saturating_add(popped_entry.length.to_usize()); + if tail < self.storage.len() { + self.tail = tail; + } else { + self.tail = Default::default(); + } + } + + pub fn read(&self, data_entry: &RaftLogDataEntry) -> Option>> { + if let Ok(data) = self.storage.read(data_entry.offset, data_entry.length.to_usize(), data_entry.nonce) { + Some(data) + } else { + error!("error reading raft log entry at offset {} length {}", data_entry.offset, data_entry.length); + None + } + } +} diff --git a/enclave/kbupd_enclave/src/storage/raft_log/raft_log_index.rs b/enclave/kbupd_enclave/src/storage/raft_log/raft_log_index.rs new file mode 100644 index 0000000..5452d8a --- /dev/null +++ b/enclave/kbupd_enclave/src/storage/raft_log/raft_log_index.rs @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::prelude::*; + +use crate::protobufs::raft::*; +use crate::storage::*; +use crate::util::*; + +use super::raft_log_data::*; + +use std::num::*; + +use bytes::*; +use num_traits::{ToPrimitive}; + +pub struct RaftLogIndex { + storage: StorageArray, + + tail: u32, + len: u32, + + prev_log_idx: LogIdx, + prev_log_term: Option, + last_log_term: TermId, +} + +#[derive(Clone)] +pub struct RaftLogIndexEntry { + pub term: TermId, + pub data: Option, +} + +impl RaftLogIndex { + pub fn new(index_size: u32, cache_size: usize) -> Result { + Ok(Self { + storage: StorageArray::new(index_size, cache_size)?, + tail: Default::default(), + len: Default::default(), + prev_log_idx: Default::default(), + prev_log_term: Default::default(), + last_log_term: Default::default(), + }) + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn is_full(&self) -> bool { + self.len >= self.storage.len() + } + + pub fn prev_log_idx(&self) -> LogIdx { + self.prev_log_idx + } + + pub fn prev_log_term(&self) -> Option { + self.prev_log_term + } + + pub fn last_log_idx(&self) -> LogIdx { + self.prev_log_idx + u64::from(self.len) + } + + pub fn last_log_term(&self) -> TermId { + self.last_log_term + } + + pub fn set_cache_size(&mut self, cache_size: usize) { + self.storage.set_cache_size(cache_size); + } + + pub fn get(&mut self, log_idx: LogIdx) -> Option<&RaftLogIndexEntry> { + let offset: u64 = log_idx.id.checked_sub((self.prev_log_idx + 1).id)?; + if offset < u64::from(self.len) { + let index = self.wrap_add(offset.to_u32().unwrap_or_else(|| unreachable!())); + match self.storage.get(index).unwrap_or_else(|| panic!("overflow")) { + Ok(slot) => slot, + Err(storage_error) => { + error!("storage error reading from raft log index {} (current length {}): {}", + index, self.len, storage_error); + None + } + } + } else { + None + } + } + + pub fn push_back(&mut self, entry: RaftLogIndexEntry) -> Result<(), ()> { + if self.len < self.storage.len() { + let index = self.wrap_add(self.len); + match self.storage.get_mut(index).unwrap_or_else(|| panic!("overflow")) { + Ok(slot) => { + self.len = self.len.checked_add(1).unwrap_or_else(|| unreachable!()); + self.last_log_term = entry.term; + *slot = Some(entry); + Ok(()) + } + Err(storage_error) => { + error!("storage error appending to raft log index {} (current length {}): {}", + index, self.len, storage_error); + Err(()) + } + } + } else { + Err(()) + } + } + + pub fn pop_front(&mut self) -> Option { + if let Some(new_len) = self.len.checked_sub(1) { + let entry = match self.storage.get(self.tail).unwrap_or_else(|| panic!("overflow")) { + Ok(entry) => entry.cloned(), + Err(storage_error) => { + error!("storage error popping from raft log index {} (current length {}): {}", + self.tail, self.len, storage_error); + None + } + }; + self.prev_log_idx = self.prev_log_idx + 1; + self.prev_log_term = entry.as_ref().map(|entry| entry.term); + self.tail = self.wrap_add(1); + self.len = new_len; + entry + } else { + None + } + } + + pub fn cancel_from(&mut self, from_log_idx: LogIdx) -> Result, ()> { + let mut cancelled = Vec::new(); + let from_offset: u64 = from_log_idx.id.checked_sub((self.prev_log_idx + 1).id).ok_or(())?; + let from_offset: u32 = from_offset.to_u32().ok_or(())?; + if let Some(cancel_len) = self.len.checked_sub(from_offset) { + if let Some(cancel_len) = NonZeroU32::new(cancel_len) { + let new_last_log_term = if from_offset != 0 { + if let Some(new_last_log_entry) = self.get(self.prev_log_idx + u64::from(from_offset)) { + new_last_log_entry.term + } else { + error!("error reading raft log index entry preceding {} to cancel", from_log_idx); + return Err(()); + } + } else { + if self.prev_log_idx == Default::default() { + Default::default() + } else if let Some(prev_log_term) = self.prev_log_term { + prev_log_term + } else { + error!("did not have prev raft log term preceding {} to cancel", from_log_idx); + return Err(()); + } + }; + + for cancel_idx in 0..cancel_len.get() { + let cancel_log_idx = from_log_idx + u64::from(cancel_idx); + if let Some(cancel_entry) = self.get(cancel_log_idx) { + if let Some(cancel_data_entry) = cancel_entry.data { + cancelled.push(cancel_data_entry); + } + } else { + error!("error reading raft log index entry {} to cancel", cancel_log_idx); + } + } + self.len = from_offset; + self.last_log_term = new_last_log_term; + } + } else { + warn!("tried to cancel non-existent raft log entries from {} (last is {})", from_log_idx, self.last_log_idx()); + } + Ok(cancelled) + } + + #[allow(clippy::integer_arithmetic)] + fn wrap_add(&self, addend: u32) -> u32 { + let index_rem = self.storage.len() - self.tail; + if addend < index_rem { + self.tail + addend + } else { + if addend <= self.storage.len() { + addend - index_rem + } else { + (addend - index_rem).checked_rem(self.storage.len()) + .unwrap_or_else(|| unreachable!()) + } + } + } +} + +impl StorageValue for RaftLogIndexEntry { + fn encoded_len() -> u32 { + 8 + 8 + 8 + 4 + } + fn encode(maybe_value: Option<&Self>, buf: &mut B) { + if let Some(value) = maybe_value { + buf.put_u64_le(value.term.id); + if let Some(data) = &value.data { + buf.put_u64_le(data.nonce.into()); + buf.put_u64_le(data.offset.to_u64()); + buf.put_u32_le(data.length); + } else { + buf.put_u64_le(0); + } + } else { + buf.put_u64_le(0); + } + } + fn decode(buf: &mut B) -> Option { + let term = buf.get_u64_le(); + if term != 0 { + let term = TermId { id: term }; + if let Some(nonce) = NonZeroU64::new(buf.get_u64_le()) { + let offset = buf.get_u64_le().to_usize(); + let length = buf.get_u32_le(); + + Some(Self { + term, + data: Some(RaftLogDataEntry { + nonce: StorageDataNonce::new(nonce), + offset, + length, + }), + }) + } else { + Some(Self { + term, + data: None, + }) + } + } else { + None + } + } +} diff --git a/enclave/kbupd_enclave/src/storage/storage_array.rs b/enclave/kbupd_enclave/src/storage/storage_array.rs new file mode 100644 index 0000000..5fcee84 --- /dev/null +++ b/enclave/kbupd_enclave/src/storage/storage_array.rs @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use super::*; + +pub struct StorageArray { + cache: StoragePageCache, +} + +// +// StorageArray impls +// + +impl StorageArray +where V: StorageValue, +{ + pub fn new(length: u32, cache_size: usize) -> Result { + let page_count = length.saturating_add(V::items_per_page() - 1) + .checked_div(V::items_per_page()) + .unwrap_or_else(|| static_unreachable!()); + Ok(Self { + cache: StoragePageCache::with_page_count(page_count, cache_size)?, + }) + } + + pub fn len(&self) -> u32 { + self.cache.page_count().saturating_mul(V::items_per_page()) + } + + pub fn set_cache_size(&mut self, cache_size: usize) { + self.cache.set_cache_size(cache_size); + } + + pub fn get(&mut self, index: u32) -> Option, StorageError>> { + let item_index = self.cache.item_index(index)?; + Some(self.cache.get_item(&item_index)) + } + + pub fn get_mut(&mut self, index: u32) -> Option, StorageError>> { + let item_index = self.cache.item_index(index)?; + Some(self.cache.get_item_mut(&item_index)) + } +} diff --git a/enclave/kbupd_enclave/src/storage/storage_data.rs b/enclave/kbupd_enclave/src/storage/storage_data.rs new file mode 100644 index 0000000..c5ced75 --- /dev/null +++ b/enclave/kbupd_enclave/src/storage/storage_data.rs @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::prelude::*; + +use std::convert::{TryInto}; +use std::num::*; + +use sgx_ffi::untrusted_slice::*; +use sgx_ffi::util::{SecretValue}; +use sgxsd_ffi::{AesGcmIv, AesGcmKey, AesGcmMac}; + +use crate::ffi::ecalls::{kbupd_enclave_alloc_untrusted}; + +const TAG_LENGTH: usize = StorageData::tag_len() as usize; + +pub struct StorageData { + data: UntrustedSlice<'static>, + cipher: AesGcmKey, + nonce: NonZeroU64, +} + +#[derive(Clone, Copy)] +pub struct StorageDataNonce(NonZeroU64); + +// +// StorageData impls +// + +impl StorageData { + pub fn new(data_size: usize) -> Result { + let data = if let Some(data_size) = NonZeroUsize::new(data_size) { + kbupd_enclave_alloc_untrusted(data_size.get())? + } else { + UntrustedSlice::Empty + }; + Ok(Self { + cipher: Default::default(), + data, + nonce: NonZeroU64::new(1).unwrap_or_else(|| static_unreachable!()), + }) + } + + pub const fn tag_len() -> u8 { + 16 + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn read(&self, offset: usize, read_len: usize, nonce: StorageDataNonce) -> Result>, ()> { + let data_len = read_len.checked_sub(TAG_LENGTH).ok_or(())?; + let mut data = self.data.offset(offset).read_bytes(read_len)?; + + let mac_data: &[u8] = data.get(data_len..read_len).unwrap_or_else(|| unreachable!()); + let mac_data: &[u8; TAG_LENGTH] = mac_data.try_into().unwrap_or_else(|_| unreachable!()); + let mac = AesGcmMac { data: *mac_data }; + + let mut iv = AesGcmIv::default(); + let iv_data: &mut [u8] = iv.data.get_mut(4..).unwrap_or_else(|| static_unreachable!()); + let iv_data: &mut [u8; 8] = iv_data.try_into().unwrap_or_else(|_| static_unreachable!()); + *iv_data = nonce.0.get().to_be_bytes(); + + data.truncate(data_len); + + let mut data = SecretValue::new(data); + match self.cipher.decrypt(data.get_mut(), &[], &iv, &mac) { + Ok(()) => Ok(data), + Err(error) => { + error!("error decrypting storage data at offset {} length {}: {}", offset, read_len, error); + Err(()) + } + } + } + pub fn write(&mut self, offset: usize, mut data: SecretValue>) -> Result { + let nonce = self.nonce; + self.nonce = NonZeroU64::new(self.nonce.get().checked_add(1).ok_or(())?) + .unwrap_or_else(|| unreachable!()); + + let mut iv = AesGcmIv::default(); + let iv_data: &mut [u8] = iv.data.get_mut(4..).unwrap_or_else(|| static_unreachable!()); + let iv_data: &mut [u8; 8] = iv_data.try_into().unwrap_or_else(|_| static_unreachable!()); + *iv_data = nonce.get().to_be_bytes(); + + let mut mac = AesGcmMac { data: Default::default() }; + + match self.cipher.encrypt(data.get_mut(), &[], &iv, &mut mac) { + Ok(()) => { + self.data.offset(offset).write_bytes(data.get())?; + self.data.offset(offset.saturating_add(data.get().len())).write_bytes(&mac.data)?; + // no need to erase, as data is encrypted now + data.get_mut().clear(); + Ok(StorageDataNonce(nonce)) + } + Err(error) => { + error!("error encrypting storage data at offset {} length {}: {}", offset, data.get().len(), error); + Err(()) + } + } + } +} + +impl StorageDataNonce { + pub fn new(value: NonZeroU64) -> Self { + Self(value) + } +} + +impl From for u64 { + fn from(from: StorageDataNonce) -> Self { + from.0.get() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::ffi::mocks; + use mockers::*; + + struct TestStorageData { + storage: Option, + data: Option<(*mut u8, usize)>, + } + + impl TestStorageData { + fn new(scenario: &Scenario, want_size: usize) -> Self { + if want_size != 0 { + let mut data_vec: Vec = Vec::with_capacity(want_size); + let data: *mut u8 = data_vec.as_mut_ptr(); + let size: usize = data_vec.capacity(); + std::mem::forget(data_vec); + + mocks::expect_kbupd_enclave_ocall_alloc(scenario, size, data as *mut libc::c_void, size); + let storage = StorageData::new(size).unwrap(); + assert_eq!(storage.len(), size); + Self { storage: Some(storage), data: Some((data, size)) } + } else { + let storage = StorageData::new(0).unwrap(); + assert_eq!(storage.len(), 0); + Self { storage: Some(storage), data: None } + } + } + fn get_mut(&mut self) -> &mut StorageData { + self.storage.as_mut().unwrap() + } + } + + impl Drop for TestStorageData { + fn drop(&mut self) { + drop(self.storage.take()); + if let Some((data, size)) = self.data.take() { + unsafe { Vec::from_raw_parts(data, 0, size) }; + } + } + } + + #[test] + fn test_storage_data_invalid_empty() { + let scenario = Scenario::new(); + let nonce = StorageDataNonce::new(NonZeroU64::new(1).unwrap()); + let mut storage = TestStorageData::new(&scenario, 0); + + for &offset in &[0, 1, usize::max_value()] { + for &length in &[0, 1, usize::max_value()] { + assert!(storage.get_mut().read(offset, length, nonce).is_err()); + } + for &length in &[0, 1] { + assert!(storage.get_mut().write(offset, SecretValue::new(vec![0; length])).is_err()); + } + } + } + + #[test] + fn test_storage_data_valid() { + let scenario = Scenario::new(); + let nonce = StorageDataNonce::new(NonZeroU64::new(1).unwrap()); + let mut storage = TestStorageData::new(&scenario, 2 + TAG_LENGTH); + + for &offset in &[0, 1] { + for &length in &[0, 1] { + assert!(storage.get_mut().write(offset, SecretValue::new(vec![0; length])).is_ok()); + assert_eq!(storage.get_mut().read(offset, length + TAG_LENGTH, nonce).unwrap().get().len(), length); + } + } + assert!(storage.get_mut().write(2, SecretValue::new(vec![])).is_ok()); + assert_eq!(storage.get_mut().read(2, TAG_LENGTH, nonce).unwrap().get().len(), 0); + } + + #[test] + fn test_storage_invalid_overflow() { + let scenario = Scenario::new(); + let nonce = StorageDataNonce::new(NonZeroU64::new(1).unwrap()); + let mut storage = TestStorageData::new(&scenario, TAG_LENGTH - 1); + + assert!(storage.get_mut().read(0, 0, nonce).is_err()); + assert!(storage.get_mut().read(0, TAG_LENGTH - 1, nonce).is_err()); + + assert!(storage.get_mut().write(0, SecretValue::new(vec![])).is_err()); + assert!(storage.get_mut().read(0, TAG_LENGTH, nonce).is_err()); + + let scenario = Scenario::new(); + let mut storage = TestStorageData::new(&scenario, TAG_LENGTH); + + assert!(storage.get_mut().write(0, SecretValue::new(vec![])).is_ok()); + assert!(storage.get_mut().read(0, TAG_LENGTH, nonce).is_ok()); + + assert!(storage.get_mut().write(0, SecretValue::new(vec![0; 1])).is_err()); + assert!(storage.get_mut().read(0, TAG_LENGTH + 1, nonce).is_err()); + assert!(storage.get_mut().read(0, usize::max_value(), nonce).is_err()); + } + + #[test] + fn test_storage_invalid_integer_overflow() { + let scenario = Scenario::new(); + let nonce = StorageDataNonce::new(NonZeroU64::new(1).unwrap()); + + let data: *mut u8 = unsafe { std::ptr::NonNull::dangling().as_mut() }; + let size: usize = usize::max_value() - 1; + + mocks::expect_kbupd_enclave_ocall_alloc(&scenario, size, data as *mut libc::c_void, size); + let storage = StorageData::new(size).unwrap(); + + assert!(storage.read(0, usize::max_value(), nonce).is_err()); + + assert!(storage.read(size - TAG_LENGTH + 1, TAG_LENGTH, nonce).is_err()); + assert!(storage.read(size, TAG_LENGTH, nonce).is_err()); + assert!(storage.read(usize::max_value(), TAG_LENGTH, nonce).is_err()); + + assert!(storage.read(usize::max_value(), usize::max_value(), nonce).is_err()); + } +} diff --git a/enclave/kbupd_enclave/src/storage/storage_page_cache.rs b/enclave/kbupd_enclave/src/storage/storage_page_cache.rs new file mode 100644 index 0000000..0a9888b --- /dev/null +++ b/enclave/kbupd_enclave/src/storage/storage_page_cache.rs @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::prelude::*; + +use std::fmt; +use std::marker::*; +use std::rc::*; + +use bytes::*; +use num_traits::{ToPrimitive}; + +use crate::lru::*; +use crate::storage::storage_data::*; +use crate::util::*; + +const PAGE_SIZE: u16 = 4096; + +pub struct StoragePageCache { + cache_size: usize, + + pages: Box<[StoragePage]>, + cached: Lru, + data: StorageData, +} + +#[derive(Debug)] +pub enum StorageError { + InternalError, + ReadError, +} + +pub trait StorageValue: Sized { + fn encoded_len() -> u32; + fn encode(value: Option<&Self>, buf: &mut B); + fn decode(buf: &mut B) -> Option; + fn encode_to_vec(&self) -> Vec { + let mut encoded = Vec::with_capacity(Self::encoded_len().to_usize()); + Self::encode(Some(self), &mut encoded); + encoded + } + fn items_per_page() -> u32 { + (u32::from(PAGE_SIZE) - u32::from(StorageData::tag_len())) / Self::encoded_len() + } +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct StoragePageIndex(u32); + +pub struct StorageItemIndex { + index: u32, + _data: PhantomData, +} + +enum StoragePage { + Free, + Poisoned, + Uncached(UncachedStoragePage), + Cached(Box>), +} + +enum CachedStoragePageDirtyState { + Dirty, + Clean(StorageDataNonce), +} + +struct CachedStoragePage { + dirty: CachedStoragePageDirtyState, + lru_entry: Weak>, + items: Box<[Option]>, +} + +struct UncachedStoragePage { + nonce: StorageDataNonce, +} + +impl StoragePageCache +where V: StorageValue, +{ + pub fn with_page_count(page_count: u32, cache_size: usize) -> Result { + let data_size = page_count.to_usize().checked_mul(PAGE_SIZE.into()).ok_or(())?; + Self::new(data_size, cache_size) + } + pub fn new(data_size: usize, cache_size: usize) -> Result { + let cache_size = cache_size.max(1); + let data = StorageData::new(data_size)?; + let max_page_count = StorageItemIndex::::max_page_count().to_usize(); + let page_count = (data.len() / usize::from(PAGE_SIZE)).max(1) + .min(max_page_count); + + let mut pages: Vec> = Vec::with_capacity(page_count); + pages.extend(std::iter::repeat_with(Default::default).take(page_count)); + + Ok(Self { + cache_size, + + pages: pages.into(), + cached: Default::default(), + data, + }) + } + + pub fn page_count(&self) -> u32 { + self.pages.len().to_u32().unwrap_or(u32::max_value()) + } + + pub fn item_index(&self, index: u32) -> Option> { + let entry = StorageItemIndex { + index, + _data: PhantomData, + }; + if u32::from(entry.page_index()) < self.page_count() { + Some(entry) + } else { + None + } + } + + pub fn set_cache_size(&mut self, cache_size: usize) { + self.cache_size = cache_size; + } + + pub fn get_item(&mut self, entry: &StorageItemIndex) -> Result, StorageError> { + let cached_page = self.read_page(entry.page_index())?; + Ok(cached_page.get_item(entry)) + } + + pub fn get_item_mut(&mut self, entry: &StorageItemIndex) -> Result<&mut Option, StorageError> { + let cached_page = self.read_page(entry.page_index())?; + Ok(cached_page.get_item_mut(entry)) + } + + fn read_page(&mut self, page_index: StoragePageIndex) -> Result<&mut CachedStoragePage, StorageError> { + match self.pages.get(usize::from(page_index)) { + Some(StoragePage::Free) | + Some(StoragePage::Uncached(_)) => { + while self.cached.len() >= self.cache_size { + if let Some(evict_lru_entry) = self.cached.pop_front() { + debug!("evicting storage page {}", evict_lru_entry.get()); + self.write_page(*evict_lru_entry.get()); + } else { + break; + } + } + } + None | + Some(StoragePage::Poisoned) | + Some(StoragePage::Cached(_)) => { + } + } + + if let Some(page) = self.pages.get_mut(usize::from(page_index)) { + let cached_page = match page.read(&self.data, &mut self.cached, page_index) { + Ok(cached_page) => cached_page, + Err(storage_error) => { + error!("fatal error reading page {}: {}", page_index, storage_error); + return Err(storage_error); + } + }; + + self.cached.bump(&cached_page.lru_entry); + + Ok(cached_page) + } else { + error!("fetching out of bounds page {}!", page_index); + Err(StorageError::InternalError) + } + } + + fn write_page(&mut self, page_index: StoragePageIndex) { + if let Some(page) = self.pages.get_mut(usize::from(page_index)) { + page.write(&mut self.data, page_index); + } else { + error!("evicting out of bounds page {}!", &page_index); + } + } +} + +impl fmt::Display for StorageError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, fmt) + } +} + +impl fmt::Display for StoragePageIndex { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self(index) = self; + fmt::Display::fmt(index, fmt) + } +} + +impl From for u32 { + fn from(from: StoragePageIndex) -> Self { + from.0 + } +} + +impl From for usize { + fn from(from: StoragePageIndex) -> Self { + from.0.to_usize() + } +} + +impl StorageItemIndex +where V: StorageValue, +{ + fn page_index(&self) -> StoragePageIndex { + StoragePageIndex(self.index / V::items_per_page()) + } + fn item_index(&self) -> usize { + (self.index % V::items_per_page()).to_usize() + } + fn max_page_count() -> u32 { + u32::max_value() / V::items_per_page() + } +} + +impl StoragePage +where V: StorageValue, +{ + fn read(&mut self, + data: &StorageData, + cached: &mut Lru, + page_index: StoragePageIndex) + -> Result<&mut CachedStoragePage, StorageError> + { + match self { + StoragePage::Cached(cached_page) => { + Ok(cached_page) + } + StoragePage::Uncached(uncached_page) => { + debug!("reading storage page {}", page_index); + let lru_entry = cached.push_back(page_index); + let mut cached_page = Box::new(CachedStoragePage::new(CachedStoragePageDirtyState::Clean(uncached_page.nonce), lru_entry)); + let offset = usize::from(page_index).checked_mul(PAGE_SIZE.into()) + .ok_or(StorageError::InternalError)?; + match data.read(offset, PAGE_SIZE.into(), uncached_page.nonce) { + Ok(decrypted) => { + let items_data = decrypted.get()[..].chunks(V::encoded_len().to_usize()); + for (item, mut item_data) in cached_page.items.iter_mut().zip(items_data) { + *item = V::decode(&mut item_data); + } + + *self = StoragePage::Cached(cached_page); + match self { + StoragePage::Cached(cached_page) => Ok(cached_page), + _ => static_unreachable!(), + } + } + Err(()) => { + *self = StoragePage::Poisoned; + Err(StorageError::ReadError) + } + } + } + StoragePage::Poisoned => { + Err(StorageError::ReadError) + } + StoragePage::Free => { + let lru_entry = cached.push_back(page_index); + *self = StoragePage::Cached(Box::new(CachedStoragePage::new(CachedStoragePageDirtyState::Dirty, lru_entry))); + match self { + StoragePage::Cached(cached_page) => Ok(cached_page), + _ => static_unreachable!(), + } + } + } + } + fn write(&mut self, data: &mut StorageData, page_index: StoragePageIndex) { + let cached_page = match self { + StoragePage::Cached(cached_page) => cached_page, + _ => return, + }; + + *self = match cached_page.dirty { + CachedStoragePageDirtyState::Clean(nonce) => { + StoragePage::Uncached(UncachedStoragePage { nonce }) + } + CachedStoragePageDirtyState::Dirty if cached_page.is_empty() => { + StoragePage::Free + } + CachedStoragePageDirtyState::Dirty => { + let mut secret_encoded_vec = SecretValue::new(Vec::with_capacity(PAGE_SIZE.into())); + let encoded: &mut Vec = secret_encoded_vec.get_mut(); + for (item_index, item) in cached_page.items.iter().enumerate() { + encoded.resize(item_index.saturating_mul(V::encoded_len().to_usize()), 0); + V::encode(item.as_ref(), encoded); + } + encoded.resize(usize::from(PAGE_SIZE) - usize::from(StorageData::tag_len()), 0); + + let offset_res = usize::from(page_index).checked_mul(PAGE_SIZE.into()).ok_or(()); + match offset_res.and_then(|offset: usize| data.write(offset, secret_encoded_vec)) { + Ok(nonce) => { + StoragePage::Uncached(UncachedStoragePage { nonce }) + } + Err(()) => { + error!("wrote out of bounds page {}!", page_index); + return; + } + } + } + }; + } +} + +impl Default for StoragePage { + fn default() -> Self { + StoragePage::Free + } +} + +impl CachedStoragePage +where V: StorageValue, +{ + fn new(dirty: CachedStoragePageDirtyState, lru_entry: Weak>) -> Self { + let size = V::items_per_page(); + let mut items = Vec::with_capacity(size.to_usize()); + items.extend(std::iter::repeat_with(Default::default).take(size.to_usize())); + Self { + dirty, + lru_entry, + items: items.into(), + } + } + fn get_item(&mut self, item_index: &StorageItemIndex) -> Option<&V> { + self.items.get(item_index.item_index()) + .unwrap_or_else(|| panic!("overflow")) + .as_ref() + } + fn get_item_mut(&mut self, item_index: &StorageItemIndex) -> &mut Option { + self.dirty = CachedStoragePageDirtyState::Dirty; + self.items.get_mut(item_index.item_index()) + .unwrap_or_else(|| panic!("overflow")) + } + fn is_empty(&self) -> bool { + for item_slot in self.items.iter() { + if item_slot.is_some() { + return false; + } + } + true + } +} diff --git a/enclave/kbupd_enclave/src/util.rs b/enclave/kbupd_enclave/src/util.rs new file mode 100644 index 0000000..3850ec8 --- /dev/null +++ b/enclave/kbupd_enclave/src/util.rs @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::prelude::*; + +use std::fmt; + +use base64; +use serde::{Deserialize, Deserializer}; +use serde::de::{Error}; + +pub use sgx_ffi::util::*; +pub use crate::protobufs::kbupd::*; + +pub struct ToHex<'a>(pub &'a [u8]); + +impl<'a> fmt::Display for ToHex<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let ToHex(data) = self; + for byte in *data { + write!(fmt, "{:02x}", byte)?; + } + Ok(()) + } +} +impl<'a> fmt::Debug for ToHex<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +pub struct OptionDisplay(pub Option); + +impl fmt::Display for OptionDisplay +where T: fmt::Display, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let Self(inner) = self; + match inner { + Some(inner) => fmt::Display::fmt(inner, fmt), + None => write!(fmt, ""), + } + } +} + +impl fmt::Debug for OptionDisplay +where T: fmt::Display, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Display::fmt(self, fmt) + } +} + +pub struct ListDisplay(pub T); + +impl fmt::Display for ListDisplay +where T: IntoIterator + Clone, + T::Item: fmt::Display, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let Self(inner) = self; + fmt.debug_list() + .entries(inner.clone().into_iter().map(DisplayAsDebug)) + .finish() + } +} + +impl fmt::Debug for ListDisplay +where T: IntoIterator + Clone, + T::Item: fmt::Display, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Display::fmt(self, fmt) + } +} + +pub struct DisplayAsDebug(pub T); + +impl fmt::Debug for DisplayAsDebug +where T: fmt::Display, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let Self(inner) = self; + fmt::Display::fmt(inner, fmt) + } +} + +pub fn deserialize_base64<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + Deserialize::deserialize(deserializer) + .and_then(|base64: &[u8]| { + base64::decode(base64).map_err(|error| D::Error::custom(error.to_string())) + }) +} + +pub fn copy_exact(src: &[u8]) -> Result +where T: AsMut<[u8]> + Default +{ + let mut dst = T::default(); + if src.len() == dst.as_mut().len() { + dst.as_mut().copy_from_slice(src); + Ok(dst) + } else { + Err(()) + } +} + +pub fn memory_status() -> EnclaveMemoryStatus { + let memory_status = MemoryStatus::collect(); + EnclaveMemoryStatus { + footprint_bytes: memory_status.footprint_bytes, + used_bytes: memory_status.used_bytes, + free_chunks: memory_status.free_chunks, + } +} + +pub trait ToUsize { + fn to_usize(self) -> usize; +} + +#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] +impl ToUsize for u32 { + fn to_usize(self) -> usize { + self as usize + } +} + +#[allow(clippy::cast_possible_truncation)] +#[cfg(any(target_pointer_width = "64"))] +impl ToUsize for u64 { + fn to_usize(self) -> usize { + self as usize + } +} + +pub trait ToU64 { + fn to_u64(self) -> u64; +} + +#[cfg(any(target_pointer_width = "64"))] +impl ToU64 for usize { + fn to_u64(self) -> u64 { + self as u64 + } +} diff --git a/enclave/libkbupd_enclave.config.xml b/enclave/libkbupd_enclave.config.xml new file mode 100644 index 0000000..b1a5d6b --- /dev/null +++ b/enclave/libkbupd_enclave.config.xml @@ -0,0 +1,12 @@ + + 0 + 0 + 0x40000 + 0x40000000 + 1 + 0 + + 1 + 0 + 0xFFFFFFFF + diff --git a/enclave/libkbupd_enclave.debug.config.xml b/enclave/libkbupd_enclave.debug.config.xml new file mode 100644 index 0000000..930bfed --- /dev/null +++ b/enclave/libkbupd_enclave.debug.config.xml @@ -0,0 +1,12 @@ + + 0 + 0 + 0x40000 + 0x10000000 + 1 + 0 + + 0 + 0 + 0xFFFFFFFF + diff --git a/enclave/libkbupd_enclave.hardened.config.xml b/enclave/libkbupd_enclave.hardened.config.xml new file mode 100644 index 0000000..b1a5d6b --- /dev/null +++ b/enclave/libkbupd_enclave.hardened.config.xml @@ -0,0 +1,12 @@ + + 0 + 0 + 0x40000 + 0x40000000 + 1 + 0 + + 1 + 0 + 0xFFFFFFFF + diff --git a/enclave/libkbupd_enclave.hardened.debug.config.xml b/enclave/libkbupd_enclave.hardened.debug.config.xml new file mode 100644 index 0000000..930bfed --- /dev/null +++ b/enclave/libkbupd_enclave.hardened.debug.config.xml @@ -0,0 +1,12 @@ + + 0 + 0 + 0x40000 + 0x10000000 + 1 + 0 + + 0 + 0 + 0xFFFFFFFF + diff --git a/enclave/libkbupd_enclave.hardened.test.config.xml b/enclave/libkbupd_enclave.hardened.test.config.xml new file mode 100644 index 0000000..85b8627 --- /dev/null +++ b/enclave/libkbupd_enclave.hardened.test.config.xml @@ -0,0 +1,12 @@ + + 0 + 0 + 0x40000 + 0x10000000 + 1 + 0 + + 1 + 0 + 0xFFFFFFFF + diff --git a/enclave/libkbupd_enclave.lds b/enclave/libkbupd_enclave.lds new file mode 100644 index 0000000..f525de9 --- /dev/null +++ b/enclave/libkbupd_enclave.lds @@ -0,0 +1,10 @@ +enclave +{ + global: + g_global_data_sim; + g_global_data; + enclave_entry; + g_peak_heap_used; + local: + *; +}; diff --git a/enclave/patches/linux-sgx-rep-stringops.patch b/enclave/patches/linux-sgx-rep-stringops.patch new file mode 100644 index 0000000..2c7140e --- /dev/null +++ b/enclave/patches/linux-sgx-rep-stringops.patch @@ -0,0 +1,52 @@ +diff --git a/sdk/tlibc/string/memcmp.c b/sdk/tlibc/string/memcmp.c +index aab5c4133ca2..459228ea1745 100644 +--- a/sdk/tlibc/string/memcmp.c ++++ b/sdk/tlibc/string/memcmp.c +@@ -60,6 +60,11 @@ memcmp(const void *s1, const void *s2, size_t n) + { + #ifdef _TLIBC_USE_INTEL_FAST_STRING_ + return _intel_fast_memcmp((void*)s1, (void*)s2, n); ++#elif defined(_TLIBC_USE_REP_STRING_) ++ unsigned char s2_above, s2_below; ++ __asm__("repe cmpsb" ++ : "=@cca" (s2_above), "=@ccb" (s2_below), "+D" (s1), "+S" (s2), "+c" (n)); ++ return -s2_above | s2_below; + #else + return __memcmp(s1, s2, n); + #endif +diff --git a/sdk/tlibc/string/memcpy.c b/sdk/tlibc/string/memcpy.c +index 25d0421e45a7..7f74a05bf28a 100644 +--- a/sdk/tlibc/string/memcpy.c ++++ b/sdk/tlibc/string/memcpy.c +@@ -106,6 +106,13 @@ memcpy(void *dst0, const void *src0, size_t length) + { + #ifdef _TLIBC_USE_INTEL_FAST_STRING_ + return _intel_fast_memcpy(dst0, (void*)src0, length); ++#elif defined(_TLIBC_USE_REP_STRING_) ++ long new_dst0, new_src0, new_length; ++ __asm__ volatile("rep movsb" ++ : "=&D" (new_dst0), "=&S" (new_src0), "=&c" (new_length) ++ : "0" (dst0), "1" (src0), "2" (length) ++ : "memory"); ++ return dst0; + #else + return __memcpy(dst0, src0, length); + #endif +diff --git a/sdk/tlibc/string/memset.c b/sdk/tlibc/string/memset.c +index c9676a9191b2..fb23c2b5bdd6 100644 +--- a/sdk/tlibc/string/memset.c ++++ b/sdk/tlibc/string/memset.c +@@ -57,6 +57,13 @@ memset(void *dst, int c, size_t n) + { + #ifdef _TLIBC_USE_INTEL_FAST_STRING_ + return _intel_fast_memset(dst, (void*)c, n); ++#elif defined(_TLIBC_USE_REP_STRING_) ++ long new_n, new_dst; ++ __asm__ volatile("rep stosb" ++ : "=&c" (new_n), "=&D" (new_dst) ++ : "0" (n), "1" (dst), "a" (c) ++ : "memory"); ++ return dst; + #else + return __memset(dst, c, n); + #endif /* !_TLIBC_USE_INTEL_FAST_STRING_ */ diff --git a/enclave/prostc/.cargo/config b/enclave/prostc/.cargo/config new file mode 100644 index 0000000..fe9c8d6 --- /dev/null +++ b/enclave/prostc/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/target" diff --git a/enclave/prostc/Cargo.lock b/enclave/prostc/Cargo.lock new file mode 100644 index 0000000..be8d64a --- /dev/null +++ b/enclave/prostc/Cargo.lock @@ -0,0 +1,385 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "backtrace" +version = "0.3.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "c2-chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "failure" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fixedbitset" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "getrandom" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "iovec" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itertools" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "multimap" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "petgraph" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "prost" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "prost-derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "prost-build" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "multimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "prost-types 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "prost-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "prost-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "prostc" +version = "0.1.0" +dependencies = [ + "prost-build 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-segmentation" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wasi" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "which" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "690a62be8920ccf773ee00ef0968649b0e724cda8bd5b12286302b4ae955fdf5" +"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" +"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be" +"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" +"checksum getrandom 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "473a1265acc8ff1e808cd0a1af8cee3c2ee5200916058a2ca113c29f2d903571" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" +"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" +"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum multimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb04b9f127583ed176e163fb9ec6f3e793b87e21deedd5734a69386a18a0151" +"checksum petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f" +"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96d14b1c185652833d24aaad41c5832b0be5616a590227c1fbff57c616754b23" +"checksum prost-build 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eb788126ea840817128183f8f603dce02cb7aea25c2a0b764359d8e20010702e" +"checksum prost-derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e7dc378b94ac374644181a2247cebf59a6ec1c88b49ac77f3a94b86b79d0e11" +"checksum prost-types 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1de482a366941c8d56d19b650fac09ca08508f2a696119ee7513ad590c8bac6f" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412" +"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d" +"checksum which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b57acb10231b9493c8472b20cb57317d0679a49e0bdbee44b3b803a6473af164" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/enclave/prostc/Cargo.toml b/enclave/prostc/Cargo.toml new file mode 100644 index 0000000..c0b9bb3 --- /dev/null +++ b/enclave/prostc/Cargo.toml @@ -0,0 +1,11 @@ +[package] +authors = ["Open Whisper Systems"] +name = "prostc" +license = "AGPL-3.0-or-later" +version = "0.1.0" +edition = "2018" + +[workspace] + +[dependencies] +prost-build = "0.5" diff --git a/enclave/prostc/src/main.rs b/enclave/prostc/src/main.rs new file mode 100644 index 0000000..41e0132 --- /dev/null +++ b/enclave/prostc/src/main.rs @@ -0,0 +1,16 @@ +fn main() { + let include_paths: Vec = std::env::args().skip(2).collect(); + match std::env::args().nth(1) { + Some(proto_path) => { + prost_build::compile_protos(&[proto_path], &include_paths).unwrap(); + } + _ => { + let exec_name = match std::env::args().nth(0) { + Some(exec_name) => exec_name, + None => "kbupd".to_string() + }; + println!("Usage: {} enclave_filename", exec_name); + std::process::exit(1) + } + } +} diff --git a/enclave/sgx_enclave.mk b/enclave/sgx_enclave.mk new file mode 100644 index 0000000..ef713fc --- /dev/null +++ b/enclave/sgx_enclave.mk @@ -0,0 +1,206 @@ +SGX_MODE ?= HW +export SGX_MODE +USE_OPT_LIBS ?= 0 +export USE_OPT_LIBS + +## +## linux sdk +## + +SGX_SDK_SOURCE_GIT_REV ?= d166ff0c808e2f78d37eebf1ab614d944437eea3 +SGX_DCAP_SOURCE_GIT_REV ?= 1ac77919552d5409c28cc0cd8e88398851418ba6 + +export SGX_SDK_SOURCE_DIR = $(builddir)/linux-sgx/linux-sgx-$(SGX_SDK_SOURCE_GIT_REV) +export SGX_SDK_SOURCE_INCLUDEDIR = $(SGX_SDK_SOURCE_DIR)/common/inc +export SGX_SDK_SOURCE_LIBDIR = $(SGX_SDK_SOURCE_DIR)/build/linux + +ifneq ($(SGX_SDK_DIR),) +SGX_LIBDIR = $(SGX_SDK_DIR)/lib64 +SGX_INCLUDEDIR = $(SGX_SDK_DIR)/include +endif + +SGX_INCLUDEDIR ?= $(SGX_SDK_SOURCE_INCLUDEDIR) +export SGX_INCLUDEDIR +SGX_LIBDIR ?= $(SGX_SDK_SOURCE_LIBDIR) +export SGX_LIBDIR +SGX_SIGN ?= $(SGX_SDK_SOURCE_LIBDIR)/sgx_sign +SGX_EDGER8R ?= $(SGX_SDK_SOURCE_LIBDIR)/sgx_edger8r +SGX_SDK_MAKE = env -u LDFLAGS -u CPPFLAGS CFLAGS="-D_TLIBC_USE_REP_STRING_ -fno-jump-tables -Wno-error=implicit-fallthrough" $(MAKE) + +$(SGX_SDK_SOURCE_INCLUDEDIR): | $(SGX_SDK_SOURCE_DIR) + +$(SGX_SDK_SOURCE_LIBDIR)/libsgx_trts_sim.a: | $(SGX_SDK_SOURCE_DIR) + $(SGX_SDK_MAKE) -C $(SGX_SDK_SOURCE_DIR)/sdk simulation +$(SGX_SDK_SOURCE_LIBDIR)/libsgx_%.a: | $(SGX_SDK_SOURCE_DIR) + $(SGX_SDK_MAKE) -C $(SGX_SDK_SOURCE_DIR)/sdk $* +$(SGX_SDK_SOURCE_DIR)/sdk/selib/linux/libselib.a: | $(SGX_SDK_SOURCE_DIR) + $(SGX_SDK_MAKE) -C $(SGX_SDK_SOURCE_DIR)/sdk selib +$(SGX_SDK_SOURCE_LIBDIR)/libsgx_urts_sim.so: | $(SGX_SDK_SOURCE_DIR) + $(SGX_SDK_MAKE) -C $(SGX_SDK_SOURCE_DIR)/psw simulation +$(SGX_SDK_SOURCE_LIBDIR)/libsgx_%.so: | $(SGX_SDK_SOURCE_DIR) + $(SGX_SDK_MAKE) -C $(SGX_SDK_SOURCE_DIR)/psw $* +$(SGX_SDK_SOURCE_LIBDIR)/sgx_sign: | $(SGX_SDK_SOURCE_DIR) + $(SGX_SDK_MAKE) -C $(SGX_SDK_SOURCE_DIR)/sdk signtool +$(SGX_SDK_SOURCE_LIBDIR)/sgx_edger8r: | $(SGX_SDK_SOURCE_DIR) + $(SGX_SDK_MAKE) -C $(SGX_SDK_SOURCE_DIR)/sdk edger8r + +$(builddir)/libsgx_%.a: $(SGX_LIBDIR)/libsgx_%.a + ar mD $< $$(ar t $< | env -u LANG LC_ALL=C sort) + cp $< $@ +$(builddir)/libsgx_%.so: $(SGX_LIBDIR)/libsgx_%.so + cp $< $@ #XXX Need to sort the symbols for reproducability. +$(builddir)/libselib.a: $(SGX_SDK_SOURCE_DIR)/sdk/selib/linux/libselib.a + ar mD $< $$(ar t $< | env -u LANG LC_ALL=C sort) + cp $< $@ + +SGX_SDK_SOURCE_UNPACK_DIR = $(builddir)/linux-sgx/unpack/linux-sgx-$(SGX_SDK_SOURCE_GIT_REV) +SGX_DCAP_SOURCE_UNPACK_DIR = $(builddir)/linux-sgx/unpack/SGXDataCenterAttestationPrimitives-$(SGX_DCAP_SOURCE_GIT_REV) + +$(builddir)/linux-sgx/linux-sgx-$(SGX_SDK_SOURCE_GIT_REV): + rm -rf $(builddir)/linux-sgx/unpack/ + mkdir -p $(builddir)/linux-sgx/unpack/ + wget -O - https://github.com/intel/linux-sgx/archive/$(SGX_SDK_SOURCE_GIT_REV).tar.gz \ + | tar -xzf - -C $(builddir)/linux-sgx/unpack/ + wget -O - https://github.com/intel/SGXDataCenterAttestationPrimitives/archive/$(SGX_DCAP_SOURCE_GIT_REV).tar.gz \ + | tar -xzf - -C $(builddir)/linux-sgx/unpack/ + mv $(SGX_DCAP_SOURCE_UNPACK_DIR) $(SGX_SDK_SOURCE_UNPACK_DIR)/external/dcap_sources + patch -d $(SGX_SDK_SOURCE_UNPACK_DIR) -p 1 -T < $(patchdir)/linux-sgx-rep-stringops.patch + mv $(SGX_SDK_SOURCE_UNPACK_DIR) $@ + +## +## edger8r +## + +%_t.c: %.edl %_t.h | $(SGX_EDGER8R) + mv $*_t.h $*_t.h.bak + $(SGX_EDGER8R) --trusted --trusted-dir $(dir $@) --search-path $(SGX_INCLUDEDIR) --search-path $(includedir) $<; RES=$$?; mv $*_t.h.bak $*_t.h; exit $$RES +%_t.h: %.edl | $(SGX_EDGER8R) + $(SGX_EDGER8R) --trusted --trusted-dir $(dir $@) --search-path $(SGX_INCLUDEDIR) --search-path $(includedir) --header-only $< + +%_u.c: %.edl %_u.h | $(SGX_EDGER8R) + mv $*_u.h $*_u.h.bak + $(SGX_EDGER8R) --untrusted --untrusted-dir $(dir $@) --search-path $(SGX_INCLUDEDIR) --search-path $(includedir) $<; RES=$$?; mv $*_u.h.bak $*_u.h; exit $$RES +%_u.h: %.edl | $(SGX_EDGER8R) + $(SGX_EDGER8R) --untrusted --untrusted-dir $(dir $@) --search-path $(SGX_INCLUDEDIR) --search-path $(includedir) --header-only $< + +## +## BOLT +## + +LLVM_BOLT ?= $(builddir)/bin/llvm-bolt +BOLT_DIR = $(builddir)/bolt + +BOLT_GIT_REV = 0655e9a71f43b3fc6a87e3c9be779dc76bc9efb9 +BOLT_SRC_DIR = $(BOLT_DIR)/llvm-bolt-$(BOLT_GIT_REV) +BOLT_LLVM_GIT_REV = f137ed238db11440f03083b1c88b7ffc0f4af65e +BOLT_LLVM_SRC_DIR = $(BOLT_DIR)/llvm-$(BOLT_LLVM_GIT_REV) + +$(BOLT_SRC_DIR): + mkdir -p $(BOLT_DIR) + -rm -r $(BOLT_LLVM_SRC_DIR) + wget -O - https://github.com/llvm-mirror/llvm/archive/$(BOLT_LLVM_GIT_REV).tar.gz \ + | tar -xzf - -C $(BOLT_DIR) + wget -O - https://github.com/geogriff-signal/BOLT/archive/$(BOLT_GIT_REV).tar.gz \ + | tar -xzf - -C $(BOLT_LLVM_SRC_DIR)/tools + mv $(BOLT_LLVM_SRC_DIR)/tools/BOLT-$(BOLT_GIT_REV) $(BOLT_LLVM_SRC_DIR)/tools/llvm-bolt + patch -d $(BOLT_LLVM_SRC_DIR) -p 1 -T < $(BOLT_LLVM_SRC_DIR)/tools/llvm-bolt/llvm.patch + mv $(BOLT_LLVM_SRC_DIR) $@ +$(builddir)/bin/llvm-bolt: | $(BOLT_SRC_DIR) + mkdir -p $(BOLT_DIR)/build + @( cd $(BOLT_DIR)/build && \ + cmake -G Ninja $(CURDIR)/$(BOLT_SRC_DIR) -DLLVM_TARGETS_TO_BUILD="X86" -DCMAKE_BUILD_TYPE=Release && \ + ninja ) + mkdir -p $(builddir)/bin + strip -o $@ $(BOLT_DIR)/build/bin/llvm-bolt + +## +## linking +## + +ENCLAVE_CFLAGS = -fvisibility=hidden -fPIC -I$(SGX_INCLUDEDIR)/tlibc -fno-jump-tables -fno-builtin -ffreestanding + +ENCLAVE_LDFLAGS = \ + -Wl,-z,relro,-z,now,-z,noexecstack \ + -Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles -L$(builddir) \ + -Wl,--whole-archive -lsgx_trts -Wl,--no-whole-archive \ + -Wl,--start-group -lsgx_tstdc -lselib -Wl,--end-group \ + -Wl,-Bstatic -Wl,-Bsymbolic -Wl,--no-allow-shlib-undefined \ + -Wl,-eenclave_entry -Wl,--export-dynamic -Wl,--build-id=none \ + -Wl,--defsym,__ImageBase=0 -Wl,--emit-relocs + +$(builddir)/lib%.unstripped.so: CFLAGS += $(ENCLAVE_CFLAGS) +$(builddir)/lib%.unstripped.so: $(builddir)/%_t.o $(builddir)/libsgx_trts.a $(builddir)/libselib.a $(builddir)/libsgx_tstdc.a + $(CC) $(LDFLAGS) -o $@ $(filter %.o,$^) $(LDLIBS) \ + $(ENCLAVE_LDFLAGS) -Wl,--version-script=lib$*.lds -Wl,-soname,lib$*.so + +$(builddir)/%.hardened.unstripped.so: $(builddir)/%.unstripped.so | $(LLVM_BOLT) + $(LLVM_BOLT) -trap-old-code -use-gnu-stack -update-debug-sections -update-end -v=2 \ + -skip-funcs=$(shell cat bolt_skip_funcs.txt) \ + -eliminate-unreachable=0 -strip-rep-ret=0 -simplify-conditional-tail-calls=0 \ + -align-macro-fusion=none \ + -insert-retpolines -insert-lfences \ + -o $@ $< + +$(builddir)/%.hardened.unsigned.so: $(builddir)/%.hardened.unstripped.so + objdump -j .text --no-show-raw-insn -d $< | \ + perl -ne '$$cur{"branch"} = /^\s+[0-9a-f]+:\s+j[^m][a-z]*\s/;' \ + -e '$$cur{"lfence"} = /^\s+[0-9a-f]+:\s+lfence/;' \ + -e '$$fn = $$1 if /^[0-9a-f]+ <([^>]+)>:/;' \ + -e 'if ($$cur{"branch"} && !$$prev{"branch"} && !$$prev{"lfence"}) { print "fn $$fn:\n$$prev$$_\n"; die if !grep(/$$fn\/1/, `cat bolt_skip_funcs.txt`); };' \ + -e '%prev = %cur; $$prev = $$_;' \ + -e '$$total{$$_} += $$cur{$$_} for (keys %cur);' \ + -e 'END { print "$$_: ", $$total{$$_}, "\n" for (keys %total); }' + objdump -j .text --no-show-raw-insn -d $< | \ + egrep '^\s+[0-9a-f]+:\s+(cpuid|getsec|rdpmc|sgdt|sidt|sldt|str|vmcall|vmfunc|rdtscp?|int[0-9a-z]*|iret|syscall|sysenter)\s+' | \ + wc -l | grep -q '^0$$' + strip --strip-all $< -o $@ +$(builddir)/%.unsigned.so: $(builddir)/%.unstripped.so + strip --strip-all $< -o $@ + +## +## signing +## + +%.debug.key: + openssl genrsa -out $@ -3 3072 +%.pub: %.key + openssl rsa -out $@ -in $< -pubout + +%.hardened.config.xml: %.config.xml + cp $< $@ +%.debug.config.xml: %.config.xml + sed -e 's@1@0@' $< > $@ +$(builddir)/%.debug.signdata: $(builddir)/%.unsigned.so %.debug.config.xml | $(SGX_SIGN) + $(SGX_SIGN) gendata -out $@ -enclave $(builddir)/$*.unsigned.so -config $*.debug.config.xml +$(builddir)/%.debug.so: $(builddir)/%.unsigned.so $(builddir)/%.debug.signdata %.debug.config.xml %.debug.pub $(builddir)/%.debug.sig | $(SGX_SIGN) + $(SGX_SIGN) catsig \ + -out $@ \ + -enclave $(builddir)/$*.unsigned.so \ + -unsigned $(builddir)/$*.debug.signdata \ + -config $*.debug.config.xml \ + -key $*.debug.pub \ + -sig $(builddir)/$*.debug.sig + +%.hardened.key: %.key + cp $< $@ +%.hardened.test.key: %.key + cp $< $@ + +$(builddir)/%.test.unsigned.so: $(builddir)/%.unsigned.so + cp $< $@ + +$(builddir)/%.signdata: $(builddir)/%.unsigned.so %.config.xml | $(SGX_SIGN) + $(SGX_SIGN) gendata -out $@ -enclave $(builddir)/$*.unsigned.so -config $*.config.xml +$(builddir)/%.mrenclave: $(builddir)/%.signdata + perl -e 'undef $$/; print unpack("x188 H64", <>);' $< > $@ + @echo mrenclave: $$(cat $@) +$(builddir)/%.sig: $(builddir)/%.signdata %.key + openssl dgst -sha256 -out $@ -sign $*.key $(builddir)/$*.signdata +$(builddir)/%.signed.so: $(builddir)/%.unsigned.so $(builddir)/%.signdata %.config.xml %.pub $(builddir)/%.sig | $(SGX_SIGN) + $(SGX_SIGN) catsig \ + -out $@ \ + -enclave $(builddir)/$*.unsigned.so \ + -unsigned $(builddir)/$*.signdata \ + -config $*.config.xml \ + -key $*.pub \ + -sig $(builddir)/$*.sig diff --git a/enclave/sgx_ffi/.cargo/config b/enclave/sgx_ffi/.cargo/config new file mode 100644 index 0000000..fe9c8d6 --- /dev/null +++ b/enclave/sgx_ffi/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/target" diff --git a/enclave/sgx_ffi/Cargo.toml b/enclave/sgx_ffi/Cargo.toml new file mode 100644 index 0000000..23b81ad --- /dev/null +++ b/enclave/sgx_ffi/Cargo.toml @@ -0,0 +1,30 @@ +[package] +authors = ["Open Whisper Systems"] +name = "sgx_ffi" +version = "0.1.0" +license = "AGPL-3.0-or-later" +edition = "2018" + +[features] +default = [] +test = ["lazy_static", "mockers", "mockers_derive", "rand", "test_ffi"] + +[dependencies] +libc = { version = "0.2", default-features = false, features = [] } + +# also need these as optional dependencies for the "test" feature +lazy_static = { version = "1.4", optional = true } +mockers = { version = "0.21", optional = true } +mockers_derive = { version = "0.21", optional = true } +rand = { version = "0.7", optional = true, default-features = false, features = [] } +test_ffi = { path = "../test_ffi", optional = true } + +[dev-dependencies] +lazy_static = "1.4" +mockers = "0.21" +mockers_derive = "0.21" +rand = { version = "0.7", default-features = false, features = [] } +test_ffi = { path = "../test_ffi" } + +[lib] +doctest = false diff --git a/enclave/sgx_ffi/src/bindgen_wrapper.h b/enclave/sgx_ffi/src/bindgen_wrapper.h new file mode 100644 index 0000000..044751b --- /dev/null +++ b/enclave/sgx_ffi/src/bindgen_wrapper.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include "sgx_trts.h" +#include "sgx_quote.h" +#include "sgx_utils.h" +#include "string.h" +#include "dlmalloc.h" diff --git a/enclave/sgx_ffi/src/bindgen_wrapper.rs b/enclave/sgx_ffi/src/bindgen_wrapper.rs new file mode 100644 index 0000000..61d189d --- /dev/null +++ b/enclave/sgx_ffi/src/bindgen_wrapper.rs @@ -0,0 +1,2006 @@ +/* automatically generated by rust-bindgen */ + +#[repr(C)] +#[derive(Default)] +pub struct __IncompleteArrayField(::core::marker::PhantomData, [T; 0]); +impl __IncompleteArrayField { + #[inline] + pub const fn new() -> Self { + __IncompleteArrayField(::core::marker::PhantomData, []) + } + #[inline] + pub unsafe fn as_ptr(&self) -> *const T { + ::core::mem::transmute(self) + } + #[inline] + pub unsafe fn as_mut_ptr(&mut self) -> *mut T { + ::core::mem::transmute(self) + } + #[inline] + pub unsafe fn as_slice(&self, len: usize) -> &[T] { + ::core::slice::from_raw_parts(self.as_ptr(), len) + } + #[inline] + pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { + ::core::slice::from_raw_parts_mut(self.as_mut_ptr(), len) + } +} +impl ::core::fmt::Debug for __IncompleteArrayField { + fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + fmt.write_str("__IncompleteArrayField") + } +} +impl ::core::clone::Clone for __IncompleteArrayField { + #[inline] + fn clone(&self) -> Self { + Self::new() + } +} +pub const INT8_MIN: i32 = -128; +pub const INT16_MIN: i32 = -32768; +pub const INT32_MIN: i32 = -2147483648; +pub const INT64_MIN: i64 = -9223372036854775808; +pub const INT8_MAX: u32 = 127; +pub const INT16_MAX: u32 = 32767; +pub const INT32_MAX: u32 = 2147483647; +pub const INT64_MAX: u64 = 9223372036854775807; +pub const UINT8_MAX: u32 = 255; +pub const UINT16_MAX: u32 = 65535; +pub const UINT32_MAX: u32 = 4294967295; +pub const UINT64_MAX: i32 = -1; +pub const INT_LEAST8_MIN: i32 = -128; +pub const INT_LEAST16_MIN: i32 = -32768; +pub const INT_LEAST32_MIN: i32 = -2147483648; +pub const INT_LEAST64_MIN: i64 = -9223372036854775808; +pub const INT_LEAST8_MAX: u32 = 127; +pub const INT_LEAST16_MAX: u32 = 32767; +pub const INT_LEAST32_MAX: u32 = 2147483647; +pub const INT_LEAST64_MAX: u64 = 9223372036854775807; +pub const UINT_LEAST8_MAX: u32 = 255; +pub const UINT_LEAST16_MAX: u32 = 65535; +pub const UINT_LEAST32_MAX: u32 = 4294967295; +pub const UINT_LEAST64_MAX: i32 = -1; +pub const INT_FAST8_MIN: i32 = -128; +pub const INT_FAST16_MIN: i32 = -32768; +pub const INT_FAST32_MIN: i32 = -2147483648; +pub const INT_FAST64_MIN: i64 = -9223372036854775808; +pub const INT_FAST8_MAX: u32 = 127; +pub const INT_FAST16_MAX: u64 = 9223372036854775807; +pub const INT_FAST32_MAX: u64 = 9223372036854775807; +pub const INT_FAST64_MAX: u64 = 9223372036854775807; +pub const UINT_FAST8_MAX: u32 = 255; +pub const UINT_FAST16_MAX: i32 = -1; +pub const UINT_FAST32_MAX: i32 = -1; +pub const UINT_FAST64_MAX: i32 = -1; +pub const INTPTR_MIN: i64 = -9223372036854775808; +pub const INTPTR_MAX: u64 = 9223372036854775807; +pub const UINTPTR_MAX: i32 = -1; +pub const INTMAX_MIN: i64 = -9223372036854775808; +pub const INTMAX_MAX: u64 = 9223372036854775807; +pub const UINTMAX_MAX: i32 = -1; +pub const PTRDIFF_MIN: i64 = -9223372036854775808; +pub const PTRDIFF_MAX: u64 = 9223372036854775807; +pub const SIZE_MAX: i32 = -1; +pub const WINT_MIN: u32 = 0; +pub const WINT_MAX: u32 = 4294967295; +pub const SGX_FLAGS_INITTED: u32 = 1; +pub const SGX_FLAGS_DEBUG: u32 = 2; +pub const SGX_FLAGS_MODE64BIT: u32 = 4; +pub const SGX_FLAGS_PROVISION_KEY: u32 = 16; +pub const SGX_FLAGS_EINITTOKEN_KEY: u32 = 32; +pub const SGX_FLAGS_KSS: u32 = 128; +pub const SGX_XFRM_LEGACY: u32 = 3; +pub const SGX_XFRM_AVX: u32 = 6; +pub const SGX_XFRM_AVX512: u32 = 230; +pub const SGX_XFRM_MPX: u32 = 24; +pub const SGX_XFRM_RESERVED: i32 = -232; +pub const SGX_KEYSELECT_EINITTOKEN: u32 = 0; +pub const SGX_KEYSELECT_PROVISION: u32 = 1; +pub const SGX_KEYSELECT_PROVISION_SEAL: u32 = 2; +pub const SGX_KEYSELECT_REPORT: u32 = 3; +pub const SGX_KEYSELECT_SEAL: u32 = 4; +pub const SGX_KEYPOLICY_MRENCLAVE: u32 = 1; +pub const SGX_KEYPOLICY_MRSIGNER: u32 = 2; +pub const SGX_KEYPOLICY_NOISVPRODID: u32 = 4; +pub const SGX_KEYPOLICY_CONFIGID: u32 = 8; +pub const SGX_KEYPOLICY_ISVFAMILYID: u32 = 16; +pub const SGX_KEYPOLICY_ISVEXTPRODID: u32 = 32; +pub const SGX_KEYID_SIZE: u32 = 32; +pub const SGX_CPUSVN_SIZE: u32 = 16; +pub const SGX_CONFIGID_SIZE: u32 = 64; +pub const SGX_KEY_REQUEST_RESERVED2_BYTES: u32 = 434; +pub const SGX_HASH_SIZE: u32 = 32; +pub const SGX_MAC_SIZE: u32 = 16; +pub const SGX_REPORT_DATA_SIZE: u32 = 64; +pub const SGX_ISVEXT_PROD_ID_SIZE: u32 = 16; +pub const SGX_ISV_FAMILY_ID_SIZE: u32 = 16; +pub const SGX_TARGET_INFO_RESERVED1_BYTES: u32 = 2; +pub const SGX_TARGET_INFO_RESERVED2_BYTES: u32 = 8; +pub const SGX_TARGET_INFO_RESERVED3_BYTES: u32 = 384; +pub const SGX_REPORT_BODY_RESERVED1_BYTES: u32 = 12; +pub const SGX_REPORT_BODY_RESERVED2_BYTES: u32 = 32; +pub const SGX_REPORT_BODY_RESERVED3_BYTES: u32 = 32; +pub const SGX_REPORT_BODY_RESERVED4_BYTES: u32 = 42; +pub const SGX_PLATFORM_INFO_SIZE: u32 = 101; +pub const EXIT_FAILURE: u32 = 1; +pub const EXIT_SUCCESS: u32 = 0; +pub const RAND_MAX: u32 = 2147483647; +pub const MB_CUR_MAX: u32 = 1; +pub const SGX_SUCCESS: _status_t = 0; +pub const SGX_ERROR_UNEXPECTED: _status_t = 1; +pub const SGX_ERROR_INVALID_PARAMETER: _status_t = 2; +pub const SGX_ERROR_OUT_OF_MEMORY: _status_t = 3; +pub const SGX_ERROR_ENCLAVE_LOST: _status_t = 4; +pub const SGX_ERROR_INVALID_STATE: _status_t = 5; +pub const SGX_ERROR_FEATURE_NOT_SUPPORTED: _status_t = 8; +pub const SGX_ERROR_INVALID_FUNCTION: _status_t = 4097; +pub const SGX_ERROR_OUT_OF_TCS: _status_t = 4099; +pub const SGX_ERROR_ENCLAVE_CRASHED: _status_t = 4102; +pub const SGX_ERROR_ECALL_NOT_ALLOWED: _status_t = 4103; +pub const SGX_ERROR_OCALL_NOT_ALLOWED: _status_t = 4104; +pub const SGX_ERROR_STACK_OVERRUN: _status_t = 4105; +pub const SGX_ERROR_UNDEFINED_SYMBOL: _status_t = 8192; +pub const SGX_ERROR_INVALID_ENCLAVE: _status_t = 8193; +pub const SGX_ERROR_INVALID_ENCLAVE_ID: _status_t = 8194; +pub const SGX_ERROR_INVALID_SIGNATURE: _status_t = 8195; +pub const SGX_ERROR_NDEBUG_ENCLAVE: _status_t = 8196; +pub const SGX_ERROR_OUT_OF_EPC: _status_t = 8197; +pub const SGX_ERROR_NO_DEVICE: _status_t = 8198; +pub const SGX_ERROR_MEMORY_MAP_CONFLICT: _status_t = 8199; +pub const SGX_ERROR_INVALID_METADATA: _status_t = 8201; +pub const SGX_ERROR_DEVICE_BUSY: _status_t = 8204; +pub const SGX_ERROR_INVALID_VERSION: _status_t = 8205; +pub const SGX_ERROR_MODE_INCOMPATIBLE: _status_t = 8206; +pub const SGX_ERROR_ENCLAVE_FILE_ACCESS: _status_t = 8207; +pub const SGX_ERROR_INVALID_MISC: _status_t = 8208; +pub const SGX_ERROR_INVALID_LAUNCH_TOKEN: _status_t = 8209; +pub const SGX_ERROR_MAC_MISMATCH: _status_t = 12289; +pub const SGX_ERROR_INVALID_ATTRIBUTE: _status_t = 12290; +pub const SGX_ERROR_INVALID_CPUSVN: _status_t = 12291; +pub const SGX_ERROR_INVALID_ISVSVN: _status_t = 12292; +pub const SGX_ERROR_INVALID_KEYNAME: _status_t = 12293; +pub const SGX_ERROR_SERVICE_UNAVAILABLE: _status_t = 16385; +pub const SGX_ERROR_SERVICE_TIMEOUT: _status_t = 16386; +pub const SGX_ERROR_AE_INVALID_EPIDBLOB: _status_t = 16387; +pub const SGX_ERROR_SERVICE_INVALID_PRIVILEGE: _status_t = 16388; +pub const SGX_ERROR_EPID_MEMBER_REVOKED: _status_t = 16389; +pub const SGX_ERROR_UPDATE_NEEDED: _status_t = 16390; +pub const SGX_ERROR_NETWORK_FAILURE: _status_t = 16391; +pub const SGX_ERROR_AE_SESSION_INVALID: _status_t = 16392; +pub const SGX_ERROR_BUSY: _status_t = 16394; +pub const SGX_ERROR_MC_NOT_FOUND: _status_t = 16396; +pub const SGX_ERROR_MC_NO_ACCESS_RIGHT: _status_t = 16397; +pub const SGX_ERROR_MC_USED_UP: _status_t = 16398; +pub const SGX_ERROR_MC_OVER_QUOTA: _status_t = 16399; +pub const SGX_ERROR_KDF_MISMATCH: _status_t = 16401; +pub const SGX_ERROR_UNRECOGNIZED_PLATFORM: _status_t = 16402; +pub const SGX_ERROR_UNSUPPORTED_CONFIG: _status_t = 16403; +pub const SGX_ERROR_NO_PRIVILEGE: _status_t = 20482; +pub const SGX_ERROR_PCL_ENCRYPTED: _status_t = 24577; +pub const SGX_ERROR_PCL_NOT_ENCRYPTED: _status_t = 24578; +pub const SGX_ERROR_PCL_MAC_MISMATCH: _status_t = 24579; +pub const SGX_ERROR_PCL_SHA_MISMATCH: _status_t = 24580; +pub const SGX_ERROR_PCL_GUID_MISMATCH: _status_t = 24581; +pub const SGX_ERROR_FILE_BAD_STATUS: _status_t = 28673; +pub const SGX_ERROR_FILE_NO_KEY_ID: _status_t = 28674; +pub const SGX_ERROR_FILE_NAME_MISMATCH: _status_t = 28675; +pub const SGX_ERROR_FILE_NOT_SGX_FILE: _status_t = 28676; +pub const SGX_ERROR_FILE_CANT_OPEN_RECOVERY_FILE: _status_t = 28677; +pub const SGX_ERROR_FILE_CANT_WRITE_RECOVERY_FILE: _status_t = 28678; +pub const SGX_ERROR_FILE_RECOVERY_NEEDED: _status_t = 28679; +pub const SGX_ERROR_FILE_FLUSH_FAILED: _status_t = 28680; +pub const SGX_ERROR_FILE_CLOSE_FAILED: _status_t = 28681; +pub const SGX_ERROR_UNSUPPORTED_ATT_KEY_ID: _status_t = 32769; +pub const SGX_ERROR_ATT_KEY_CERTIFICATION_FAILURE: _status_t = 32770; +pub const SGX_ERROR_ATT_KEY_UNINITIALIZED: _status_t = 32771; +pub const SGX_ERROR_INVALID_ATT_KEY_CERT_DATA: _status_t = 32772; +pub const SGX_ERROR_PLATFORM_CERT_UNAVAILABLE: _status_t = 32773; +pub const SGX_INTERNAL_ERROR_ENCLAVE_CREATE_INTERRUPTED: _status_t = 61441; +pub type _status_t = u32; +pub use self::_status_t as sgx_status_t; +pub type __int8_t = libc::c_schar; +pub type __uint8_t = libc::c_uchar; +pub type __int16_t = libc::c_short; +pub type __uint16_t = libc::c_ushort; +pub type __int32_t = libc::c_int; +pub type __uint32_t = libc::c_uint; +pub type __int64_t = libc::c_long; +pub type __uint64_t = libc::c_ulong; +pub type __int_least8_t = __int8_t; +pub type __uint_least8_t = __uint8_t; +pub type __int_least16_t = __int16_t; +pub type __uint_least16_t = __uint16_t; +pub type __int_least32_t = __int32_t; +pub type __uint_least32_t = __uint32_t; +pub type __int_least64_t = __int64_t; +pub type __uint_least64_t = __uint64_t; +pub type __int_fast8_t = __int8_t; +pub type __uint_fast8_t = __uint8_t; +pub type __int_fast16_t = libc::c_long; +pub type __uint_fast16_t = libc::c_ulong; +pub type __int_fast32_t = libc::c_long; +pub type __uint_fast32_t = libc::c_ulong; +pub type __int_fast64_t = libc::c_long; +pub type __uint_fast64_t = libc::c_ulong; +pub type __off_t = libc::c_long; +pub type __intptr_t = __int64_t; +pub type __uintptr_t = __uint64_t; +pub type __ptrdiff_t = __int64_t; +pub type __size_t = libc::c_ulong; +pub type __ssize_t = libc::c_long; +pub type __double_t = f64; +pub type __float_t = f32; +pub type __clock_t = libc::c_long; +pub type __time_t = libc::c_long; +pub type __va_list = __builtin_va_list; +pub type __wint_t = libc::c_uint; +pub type __wctype_t = libc::c_ulong; +pub type __wctrans_t = *mut libc::c_int; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct __mbstate_t { + pub __c: libc::c_int, + pub __v: __mbstate_t__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union __mbstate_t__bindgen_ty_1 { + pub __wc: __wint_t, + pub __wcb: [libc::c_char; 4usize], + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout___mbstate_t__bindgen_ty_1() { + assert_eq!( + ::core::mem::size_of::<__mbstate_t__bindgen_ty_1>(), + 4usize, + concat!("Size of: ", stringify!(__mbstate_t__bindgen_ty_1)) + ); + assert_eq!( + ::core::mem::align_of::<__mbstate_t__bindgen_ty_1>(), + 4usize, + concat!("Alignment of ", stringify!(__mbstate_t__bindgen_ty_1)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__mbstate_t__bindgen_ty_1>())).__wc as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t__bindgen_ty_1), + "::", + stringify!(__wc) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<__mbstate_t__bindgen_ty_1>())).__wcb as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t__bindgen_ty_1), + "::", + stringify!(__wcb) + ) + ); +} +impl Default for __mbstate_t__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout___mbstate_t() { + assert_eq!( + ::core::mem::size_of::<__mbstate_t>(), + 8usize, + concat!("Size of: ", stringify!(__mbstate_t)) + ); + assert_eq!( + ::core::mem::align_of::<__mbstate_t>(), + 4usize, + concat!("Alignment of ", stringify!(__mbstate_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__mbstate_t>())).__c as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t), + "::", + stringify!(__c) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__mbstate_t>())).__v as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t), + "::", + stringify!(__v) + ) + ); +} +impl Default for __mbstate_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type __intmax_t = __int64_t; +pub type __uintmax_t = __uint64_t; +pub type wchar_t = libc::c_int; +extern "C" { + pub fn sgx_is_within_enclave(addr: *const libc::c_void, size: usize) -> libc::c_int; +} +extern "C" { + pub fn sgx_is_outside_enclave(addr: *const libc::c_void, size: usize) -> libc::c_int; +} +extern "C" { + pub fn sgx_is_enclave_crashed() -> libc::c_int; +} +extern "C" { + pub fn sgx_read_rand(rand: *mut libc::c_uchar, length_in_bytes: usize) -> sgx_status_t; +} +pub type int_least8_t = __int_least8_t; +pub type uint_least8_t = __uint_least8_t; +pub type int_least16_t = __int_least16_t; +pub type uint_least16_t = __uint_least16_t; +pub type int_least32_t = __int_least32_t; +pub type uint_least32_t = __uint_least32_t; +pub type int_least64_t = __int_least64_t; +pub type uint_least64_t = __uint_least64_t; +pub type int_fast8_t = __int_fast8_t; +pub type uint_fast8_t = __uint_fast8_t; +pub type int_fast16_t = __int_fast16_t; +pub type uint_fast16_t = __uint_fast16_t; +pub type int_fast32_t = __int_fast32_t; +pub type uint_fast32_t = __uint_fast32_t; +pub type int_fast64_t = __int_fast64_t; +pub type uint_fast64_t = __uint_fast64_t; +pub type intmax_t = __intmax_t; +pub type uintmax_t = __uintmax_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _attributes_t { + pub flags: u64, + pub xfrm: u64, +} +#[test] +fn bindgen_test_layout__attributes_t() { + assert_eq!( + ::core::mem::size_of::<_attributes_t>(), + 16usize, + concat!("Size of: ", stringify!(_attributes_t)) + ); + assert_eq!( + ::core::mem::align_of::<_attributes_t>(), + 8usize, + concat!("Alignment of ", stringify!(_attributes_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_attributes_t>())).flags as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_attributes_t), + "::", + stringify!(flags) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_attributes_t>())).xfrm as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_attributes_t), + "::", + stringify!(xfrm) + ) + ); +} +pub type sgx_attributes_t = _attributes_t; +pub type sgx_misc_select_t = u32; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_misc_attribute_t { + pub secs_attr: sgx_attributes_t, + pub misc_select: sgx_misc_select_t, +} +#[test] +fn bindgen_test_layout__sgx_misc_attribute_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_misc_attribute_t>(), + 24usize, + concat!("Size of: ", stringify!(_sgx_misc_attribute_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_misc_attribute_t>(), + 8usize, + concat!("Alignment of ", stringify!(_sgx_misc_attribute_t)) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<_sgx_misc_attribute_t>())).secs_attr as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_misc_attribute_t), + "::", + stringify!(secs_attr) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<_sgx_misc_attribute_t>())).misc_select as *const _ as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_sgx_misc_attribute_t), + "::", + stringify!(misc_select) + ) + ); +} +pub type sgx_misc_attribute_t = _sgx_misc_attribute_t; +pub type sgx_key_128bit_t = [u8; 16usize]; +pub type sgx_isv_svn_t = u16; +pub type sgx_config_svn_t = u16; +pub type sgx_config_id_t = [u8; 64usize]; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_cpu_svn_t { + pub svn: [u8; 16usize], +} +#[test] +fn bindgen_test_layout__sgx_cpu_svn_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_cpu_svn_t>(), + 16usize, + concat!("Size of: ", stringify!(_sgx_cpu_svn_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_cpu_svn_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_cpu_svn_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_cpu_svn_t>())).svn as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_cpu_svn_t), + "::", + stringify!(svn) + ) + ); +} +pub type sgx_cpu_svn_t = _sgx_cpu_svn_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_key_id_t { + pub id: [u8; 32usize], +} +#[test] +fn bindgen_test_layout__sgx_key_id_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_key_id_t>(), + 32usize, + concat!("Size of: ", stringify!(_sgx_key_id_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_key_id_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_key_id_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_key_id_t>())).id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_key_id_t), + "::", + stringify!(id) + ) + ); +} +pub type sgx_key_id_t = _sgx_key_id_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _key_request_t { + pub key_name: u16, + pub key_policy: u16, + pub isv_svn: sgx_isv_svn_t, + pub reserved1: u16, + pub cpu_svn: sgx_cpu_svn_t, + pub attribute_mask: sgx_attributes_t, + pub key_id: sgx_key_id_t, + pub misc_mask: sgx_misc_select_t, + pub config_svn: sgx_config_svn_t, + pub reserved2: [u8; 434usize], +} +#[test] +fn bindgen_test_layout__key_request_t() { + assert_eq!( + ::core::mem::size_of::<_key_request_t>(), + 512usize, + concat!("Size of: ", stringify!(_key_request_t)) + ); + assert_eq!( + ::core::mem::align_of::<_key_request_t>(), + 8usize, + concat!("Alignment of ", stringify!(_key_request_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).key_name as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(key_name) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).key_policy as *const _ as usize }, + 2usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(key_policy) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).isv_svn as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(isv_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).reserved1 as *const _ as usize }, + 6usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(reserved1) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).cpu_svn as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(cpu_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).attribute_mask as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(attribute_mask) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).key_id as *const _ as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(key_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).misc_mask as *const _ as usize }, + 72usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(misc_mask) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).config_svn as *const _ as usize }, + 76usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(config_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).reserved2 as *const _ as usize }, + 78usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(reserved2) + ) + ); +} +impl Default for _key_request_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_key_request_t = _key_request_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_measurement_t { + pub m: [u8; 32usize], +} +#[test] +fn bindgen_test_layout__sgx_measurement_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_measurement_t>(), + 32usize, + concat!("Size of: ", stringify!(_sgx_measurement_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_measurement_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_measurement_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_measurement_t>())).m as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_measurement_t), + "::", + stringify!(m) + ) + ); +} +pub type sgx_measurement_t = _sgx_measurement_t; +pub type sgx_mac_t = [u8; 16usize]; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _sgx_report_data_t { + pub d: [u8; 64usize], +} +#[test] +fn bindgen_test_layout__sgx_report_data_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_report_data_t>(), + 64usize, + concat!("Size of: ", stringify!(_sgx_report_data_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_report_data_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_report_data_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_report_data_t>())).d as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_report_data_t), + "::", + stringify!(d) + ) + ); +} +impl Default for _sgx_report_data_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_report_data_t = _sgx_report_data_t; +pub type sgx_prod_id_t = u16; +pub type sgx_isvext_prod_id_t = [u8; 16usize]; +pub type sgx_isvfamily_id_t = [u8; 16usize]; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _target_info_t { + pub mr_enclave: sgx_measurement_t, + pub attributes: sgx_attributes_t, + pub reserved1: [u8; 2usize], + pub config_svn: sgx_config_svn_t, + pub misc_select: sgx_misc_select_t, + pub reserved2: [u8; 8usize], + pub config_id: sgx_config_id_t, + pub reserved3: [u8; 384usize], +} +#[test] +fn bindgen_test_layout__target_info_t() { + assert_eq!( + ::core::mem::size_of::<_target_info_t>(), + 512usize, + concat!("Size of: ", stringify!(_target_info_t)) + ); + assert_eq!( + ::core::mem::align_of::<_target_info_t>(), + 8usize, + concat!("Alignment of ", stringify!(_target_info_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).mr_enclave as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(mr_enclave) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).attributes as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(attributes) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).reserved1 as *const _ as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(reserved1) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).config_svn as *const _ as usize }, + 50usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(config_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).misc_select as *const _ as usize }, + 52usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(misc_select) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).reserved2 as *const _ as usize }, + 56usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(reserved2) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).config_id as *const _ as usize }, + 64usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(config_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).reserved3 as *const _ as usize }, + 128usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(reserved3) + ) + ); +} +impl Default for _target_info_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_target_info_t = _target_info_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _report_body_t { + pub cpu_svn: sgx_cpu_svn_t, + pub misc_select: sgx_misc_select_t, + pub reserved1: [u8; 12usize], + pub isv_ext_prod_id: sgx_isvext_prod_id_t, + pub attributes: sgx_attributes_t, + pub mr_enclave: sgx_measurement_t, + pub reserved2: [u8; 32usize], + pub mr_signer: sgx_measurement_t, + pub reserved3: [u8; 32usize], + pub config_id: sgx_config_id_t, + pub isv_prod_id: sgx_prod_id_t, + pub isv_svn: sgx_isv_svn_t, + pub config_svn: sgx_config_svn_t, + pub reserved4: [u8; 42usize], + pub isv_family_id: sgx_isvfamily_id_t, + pub report_data: sgx_report_data_t, +} +#[test] +fn bindgen_test_layout__report_body_t() { + assert_eq!( + ::core::mem::size_of::<_report_body_t>(), + 384usize, + concat!("Size of: ", stringify!(_report_body_t)) + ); + assert_eq!( + ::core::mem::align_of::<_report_body_t>(), + 8usize, + concat!("Alignment of ", stringify!(_report_body_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).cpu_svn as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(cpu_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).misc_select as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(misc_select) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).reserved1 as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved1) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).isv_ext_prod_id as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_ext_prod_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).attributes as *const _ as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(attributes) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).mr_enclave as *const _ as usize }, + 64usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(mr_enclave) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).reserved2 as *const _ as usize }, + 96usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved2) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).mr_signer as *const _ as usize }, + 128usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(mr_signer) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).reserved3 as *const _ as usize }, + 160usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved3) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).config_id as *const _ as usize }, + 192usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(config_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).isv_prod_id as *const _ as usize }, + 256usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_prod_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).isv_svn as *const _ as usize }, + 258usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).config_svn as *const _ as usize }, + 260usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(config_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).reserved4 as *const _ as usize }, + 262usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved4) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).isv_family_id as *const _ as usize }, + 304usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_family_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).report_data as *const _ as usize }, + 320usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(report_data) + ) + ); +} +impl Default for _report_body_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_report_body_t = _report_body_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _report_t { + pub body: sgx_report_body_t, + pub key_id: sgx_key_id_t, + pub mac: sgx_mac_t, +} +#[test] +fn bindgen_test_layout__report_t() { + assert_eq!( + ::core::mem::size_of::<_report_t>(), + 432usize, + concat!("Size of: ", stringify!(_report_t)) + ); + assert_eq!( + ::core::mem::align_of::<_report_t>(), + 8usize, + concat!("Alignment of ", stringify!(_report_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_t>())).body as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_report_t), + "::", + stringify!(body) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_t>())).key_id as *const _ as usize }, + 384usize, + concat!( + "Offset of field: ", + stringify!(_report_t), + "::", + stringify!(key_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_t>())).mac as *const _ as usize }, + 416usize, + concat!( + "Offset of field: ", + stringify!(_report_t), + "::", + stringify!(mac) + ) + ); +} +impl Default for _report_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_report_t = _report_t; +pub type sgx_epid_group_id_t = [u8; 4usize]; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _spid_t { + pub id: [u8; 16usize], +} +#[test] +fn bindgen_test_layout__spid_t() { + assert_eq!( + ::core::mem::size_of::<_spid_t>(), + 16usize, + concat!("Size of: ", stringify!(_spid_t)) + ); + assert_eq!( + ::core::mem::align_of::<_spid_t>(), + 1usize, + concat!("Alignment of ", stringify!(_spid_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_spid_t>())).id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_spid_t), + "::", + stringify!(id) + ) + ); +} +pub type sgx_spid_t = _spid_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _basename_t { + pub name: [u8; 32usize], +} +#[test] +fn bindgen_test_layout__basename_t() { + assert_eq!( + ::core::mem::size_of::<_basename_t>(), + 32usize, + concat!("Size of: ", stringify!(_basename_t)) + ); + assert_eq!( + ::core::mem::align_of::<_basename_t>(), + 1usize, + concat!("Alignment of ", stringify!(_basename_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_basename_t>())).name as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_basename_t), + "::", + stringify!(name) + ) + ); +} +pub type sgx_basename_t = _basename_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _quote_nonce { + pub rand: [u8; 16usize], +} +#[test] +fn bindgen_test_layout__quote_nonce() { + assert_eq!( + ::core::mem::size_of::<_quote_nonce>(), + 16usize, + concat!("Size of: ", stringify!(_quote_nonce)) + ); + assert_eq!( + ::core::mem::align_of::<_quote_nonce>(), + 1usize, + concat!("Alignment of ", stringify!(_quote_nonce)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_quote_nonce>())).rand as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_quote_nonce), + "::", + stringify!(rand) + ) + ); +} +pub type sgx_quote_nonce_t = _quote_nonce; +pub const SGX_UNLINKABLE_SIGNATURE: sgx_quote_sign_type_t = 0; +pub const SGX_LINKABLE_SIGNATURE: sgx_quote_sign_type_t = 1; +pub type sgx_quote_sign_type_t = u32; +#[repr(C, packed)] +pub struct _quote_t { + pub version: u16, + pub sign_type: u16, + pub epid_group_id: sgx_epid_group_id_t, + pub qe_svn: sgx_isv_svn_t, + pub pce_svn: sgx_isv_svn_t, + pub xeid: u32, + pub basename: sgx_basename_t, + pub report_body: sgx_report_body_t, + pub signature_len: u32, + pub signature: __IncompleteArrayField, +} +#[test] +fn bindgen_test_layout__quote_t() { + assert_eq!( + ::core::mem::size_of::<_quote_t>(), + 436usize, + concat!("Size of: ", stringify!(_quote_t)) + ); + assert_eq!( + ::core::mem::align_of::<_quote_t>(), + 1usize, + concat!("Alignment of ", stringify!(_quote_t)) + ); +} +impl Default for _quote_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_quote_t = _quote_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _platform_info { + pub platform_info: [u8; 101usize], +} +#[test] +fn bindgen_test_layout__platform_info() { + assert_eq!( + ::core::mem::size_of::<_platform_info>(), + 101usize, + concat!("Size of: ", stringify!(_platform_info)) + ); + assert_eq!( + ::core::mem::align_of::<_platform_info>(), + 1usize, + concat!("Alignment of ", stringify!(_platform_info)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_platform_info>())).platform_info as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_platform_info), + "::", + stringify!(platform_info) + ) + ); +} +impl Default for _platform_info { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_platform_info_t = _platform_info; +#[repr(C, packed)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _update_info_bit { + pub ucodeUpdate: libc::c_int, + pub csmeFwUpdate: libc::c_int, + pub pswUpdate: libc::c_int, +} +#[test] +fn bindgen_test_layout__update_info_bit() { + assert_eq!( + ::core::mem::size_of::<_update_info_bit>(), + 12usize, + concat!("Size of: ", stringify!(_update_info_bit)) + ); + assert_eq!( + ::core::mem::align_of::<_update_info_bit>(), + 1usize, + concat!("Alignment of ", stringify!(_update_info_bit)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_update_info_bit>())).ucodeUpdate as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_update_info_bit), + "::", + stringify!(ucodeUpdate) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_update_info_bit>())).csmeFwUpdate as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(_update_info_bit), + "::", + stringify!(csmeFwUpdate) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_update_info_bit>())).pswUpdate as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_update_info_bit), + "::", + stringify!(pswUpdate) + ) + ); +} +pub type sgx_update_info_bit_t = _update_info_bit; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _att_key_id_t { + pub att_key_id: [u8; 256usize], +} +#[test] +fn bindgen_test_layout__att_key_id_t() { + assert_eq!( + ::core::mem::size_of::<_att_key_id_t>(), + 256usize, + concat!("Size of: ", stringify!(_att_key_id_t)) + ); + assert_eq!( + ::core::mem::align_of::<_att_key_id_t>(), + 1usize, + concat!("Alignment of ", stringify!(_att_key_id_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_att_key_id_t>())).att_key_id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_att_key_id_t), + "::", + stringify!(att_key_id) + ) + ); +} +impl Default for _att_key_id_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_att_key_id_t = _att_key_id_t; +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct _qe_report_info_t { + pub nonce: sgx_quote_nonce_t, + pub app_enclave_target_info: sgx_target_info_t, + pub qe_report: sgx_report_t, +} +#[test] +fn bindgen_test_layout__qe_report_info_t() { + assert_eq!( + ::core::mem::size_of::<_qe_report_info_t>(), + 960usize, + concat!("Size of: ", stringify!(_qe_report_info_t)) + ); + assert_eq!( + ::core::mem::align_of::<_qe_report_info_t>(), + 1usize, + concat!("Alignment of ", stringify!(_qe_report_info_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_qe_report_info_t>())).nonce as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_qe_report_info_t), + "::", + stringify!(nonce) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<_qe_report_info_t>())).app_enclave_target_info as *const _ + as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_qe_report_info_t), + "::", + stringify!(app_enclave_target_info) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_qe_report_info_t>())).qe_report as *const _ as usize }, + 528usize, + concat!( + "Offset of field: ", + stringify!(_qe_report_info_t), + "::", + stringify!(qe_report) + ) + ); +} +impl Default for _qe_report_info_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_qe_report_info_t = _qe_report_info_t; +extern "C" { + pub fn sgx_create_report( + target_info: *const sgx_target_info_t, + report_data: *const sgx_report_data_t, + report: *mut sgx_report_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_self_report() -> *const sgx_report_t; +} +extern "C" { + pub fn sgx_self_target(target_info: *mut sgx_target_info_t) -> sgx_status_t; +} +extern "C" { + pub fn sgx_verify_report(report: *const sgx_report_t) -> sgx_status_t; +} +extern "C" { + pub fn sgx_get_key( + key_request: *const sgx_key_request_t, + key: *mut sgx_key_128bit_t, + ) -> sgx_status_t; +} +pub type errno_t = libc::c_int; +extern "C" { + pub fn memchr(arg1: *const libc::c_void, arg2: libc::c_int, arg3: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn memcmp(arg1: *const libc::c_void, arg2: *const libc::c_void, arg3: usize) + -> libc::c_int; +} +extern "C" { + pub fn memcpy( + arg1: *mut libc::c_void, + arg2: *const libc::c_void, + arg3: usize, + ) -> *mut libc::c_void; +} +extern "C" { + pub fn memmove( + arg1: *mut libc::c_void, + arg2: *const libc::c_void, + arg3: usize, + ) -> *mut libc::c_void; +} +extern "C" { + pub fn memset(arg1: *mut libc::c_void, arg2: libc::c_int, arg3: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn strchr(arg1: *const libc::c_char, arg2: libc::c_int) -> *mut libc::c_char; +} +extern "C" { + pub fn strcmp(arg1: *const libc::c_char, arg2: *const libc::c_char) -> libc::c_int; +} +extern "C" { + pub fn strcoll(arg1: *const libc::c_char, arg2: *const libc::c_char) -> libc::c_int; +} +extern "C" { + pub fn strcspn(arg1: *const libc::c_char, arg2: *const libc::c_char) -> usize; +} +extern "C" { + pub fn strerror(arg1: libc::c_int) -> *mut libc::c_char; +} +extern "C" { + pub fn strlen(arg1: *const libc::c_char) -> usize; +} +extern "C" { + pub fn strncat( + arg1: *mut libc::c_char, + arg2: *const libc::c_char, + arg3: usize, + ) -> *mut libc::c_char; +} +extern "C" { + pub fn strncmp( + arg1: *const libc::c_char, + arg2: *const libc::c_char, + arg3: usize, + ) -> libc::c_int; +} +extern "C" { + pub fn strncpy( + arg1: *mut libc::c_char, + arg2: *const libc::c_char, + arg3: usize, + ) -> *mut libc::c_char; +} +extern "C" { + pub fn strpbrk(arg1: *const libc::c_char, arg2: *const libc::c_char) -> *mut libc::c_char; +} +extern "C" { + pub fn strrchr(arg1: *const libc::c_char, arg2: libc::c_int) -> *mut libc::c_char; +} +extern "C" { + pub fn strspn(arg1: *const libc::c_char, arg2: *const libc::c_char) -> usize; +} +extern "C" { + pub fn strstr(arg1: *const libc::c_char, arg2: *const libc::c_char) -> *mut libc::c_char; +} +extern "C" { + pub fn strtok(arg1: *mut libc::c_char, arg2: *const libc::c_char) -> *mut libc::c_char; +} +extern "C" { + pub fn strxfrm(arg1: *mut libc::c_char, arg2: *const libc::c_char, arg3: usize) -> usize; +} +extern "C" { + pub fn strlcpy(arg1: *mut libc::c_char, arg2: *const libc::c_char, arg3: usize) -> usize; +} +extern "C" { + pub fn memset_s(s: *mut libc::c_void, smax: usize, c: libc::c_int, n: usize) -> errno_t; +} +extern "C" { + pub fn strndup(arg1: *const libc::c_char, arg2: usize) -> *mut libc::c_char; +} +extern "C" { + pub fn strnlen(arg1: *const libc::c_char, arg2: usize) -> usize; +} +extern "C" { + pub fn consttime_memequal( + b1: *const libc::c_void, + b2: *const libc::c_void, + len: usize, + ) -> libc::c_int; +} +extern "C" { + pub fn bcmp(arg1: *const libc::c_void, arg2: *const libc::c_void, arg3: usize) -> libc::c_int; +} +extern "C" { + pub fn bcopy(arg1: *const libc::c_void, arg2: *mut libc::c_void, arg3: usize); +} +extern "C" { + pub fn bzero(arg1: *mut libc::c_void, arg2: usize); +} +extern "C" { + pub fn index(arg1: *const libc::c_char, arg2: libc::c_int) -> *mut libc::c_char; +} +extern "C" { + pub fn mempcpy( + arg1: *mut libc::c_void, + arg2: *const libc::c_void, + arg3: usize, + ) -> *mut libc::c_void; +} +extern "C" { + pub fn rindex(arg1: *const libc::c_char, arg2: libc::c_int) -> *mut libc::c_char; +} +extern "C" { + pub fn stpncpy( + dest: *mut libc::c_char, + src: *const libc::c_char, + n: usize, + ) -> *mut libc::c_char; +} +extern "C" { + pub fn strcasecmp(arg1: *const libc::c_char, arg2: *const libc::c_char) -> libc::c_int; +} +extern "C" { + pub fn strncasecmp( + arg1: *const libc::c_char, + arg2: *const libc::c_char, + arg3: usize, + ) -> libc::c_int; +} +extern "C" { + pub fn ffs(arg1: libc::c_int) -> libc::c_int; +} +extern "C" { + pub fn ffsl(arg1: libc::c_long) -> libc::c_int; +} +extern "C" { + pub fn ffsll(arg1: libc::c_longlong) -> libc::c_int; +} +extern "C" { + pub fn strtok_r( + arg1: *mut libc::c_char, + arg2: *const libc::c_char, + arg3: *mut *mut libc::c_char, + ) -> *mut libc::c_char; +} +extern "C" { + pub fn strerror_r(arg1: libc::c_int, arg2: *mut libc::c_char, arg3: usize) -> libc::c_int; +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct div_t { + pub quot: libc::c_int, + pub rem: libc::c_int, +} +#[test] +fn bindgen_test_layout_div_t() { + assert_eq!( + ::core::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(div_t)) + ); + assert_eq!( + ::core::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(div_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(div_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).rem as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(div_t), + "::", + stringify!(rem) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct ldiv_t { + pub quot: libc::c_long, + pub rem: libc::c_long, +} +#[test] +fn bindgen_test_layout_ldiv_t() { + assert_eq!( + ::core::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(ldiv_t)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(ldiv_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(ldiv_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).rem as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(ldiv_t), + "::", + stringify!(rem) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct lldiv_t { + pub quot: libc::c_longlong, + pub rem: libc::c_longlong, +} +#[test] +fn bindgen_test_layout_lldiv_t() { + assert_eq!( + ::core::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(lldiv_t)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(lldiv_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(lldiv_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).rem as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(lldiv_t), + "::", + stringify!(rem) + ) + ); +} +extern "C" { + pub fn abort(); +} +extern "C" { + pub fn atexit(arg1: ::core::option::Option) -> libc::c_int; +} +extern "C" { + pub fn abs(arg1: libc::c_int) -> libc::c_int; +} +extern "C" { + pub fn atof(arg1: *const libc::c_char) -> f64; +} +extern "C" { + pub fn atoi(arg1: *const libc::c_char) -> libc::c_int; +} +extern "C" { + pub fn atol(arg1: *const libc::c_char) -> libc::c_long; +} +extern "C" { + pub fn bsearch( + arg1: *const libc::c_void, + arg2: *const libc::c_void, + arg3: usize, + arg4: usize, + arg5: ::core::option::Option< + unsafe extern "C" fn( + arg1: *const libc::c_void, + arg2: *const libc::c_void, + ) -> libc::c_int, + >, + ) -> *mut libc::c_void; +} +extern "C" { + pub fn calloc(arg1: usize, arg2: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn div(arg1: libc::c_int, arg2: libc::c_int) -> div_t; +} +extern "C" { + pub fn free(arg1: *mut libc::c_void); +} +extern "C" { + pub fn labs(arg1: libc::c_long) -> libc::c_long; +} +extern "C" { + pub fn ldiv(arg1: libc::c_long, arg2: libc::c_long) -> ldiv_t; +} +extern "C" { + pub fn malloc(arg1: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn memalign(arg1: usize, arg2: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn qsort( + arg1: *mut libc::c_void, + arg2: usize, + arg3: usize, + arg4: ::core::option::Option< + unsafe extern "C" fn( + arg1: *const libc::c_void, + arg2: *const libc::c_void, + ) -> libc::c_int, + >, + ); +} +extern "C" { + pub fn realloc(arg1: *mut libc::c_void, arg2: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn strtod(arg1: *const libc::c_char, arg2: *mut *mut libc::c_char) -> f64; +} +extern "C" { + pub fn strtol( + arg1: *const libc::c_char, + arg2: *mut *mut libc::c_char, + arg3: libc::c_int, + ) -> libc::c_long; +} +extern "C" { + pub fn strtof(arg1: *const libc::c_char, arg2: *mut *mut libc::c_char) -> f32; +} +extern "C" { + pub fn atoll(arg1: *const libc::c_char) -> libc::c_longlong; +} +extern "C" { + pub fn llabs(arg1: libc::c_longlong) -> libc::c_longlong; +} +extern "C" { + pub fn lldiv(arg1: libc::c_longlong, arg2: libc::c_longlong) -> lldiv_t; +} +extern "C" { + pub fn strtoll( + arg1: *const libc::c_char, + arg2: *mut *mut libc::c_char, + arg3: libc::c_int, + ) -> libc::c_longlong; +} +extern "C" { + pub fn strtoul( + arg1: *const libc::c_char, + arg2: *mut *mut libc::c_char, + arg3: libc::c_int, + ) -> libc::c_ulong; +} +extern "C" { + pub fn strtold(arg1: *const libc::c_char, arg2: *mut *mut libc::c_char) -> u128; +} +extern "C" { + pub fn strtoull( + arg1: *const libc::c_char, + arg2: *mut *mut libc::c_char, + arg3: libc::c_int, + ) -> libc::c_ulonglong; +} +extern "C" { + pub fn mblen(arg1: *const libc::c_char, arg2: usize) -> libc::c_int; +} +extern "C" { + pub fn mbstowcs(arg1: *mut wchar_t, arg2: *const libc::c_char, arg3: usize) -> usize; +} +extern "C" { + pub fn wctomb(arg1: *mut libc::c_char, arg2: wchar_t) -> libc::c_int; +} +extern "C" { + pub fn mbtowc(arg1: *mut wchar_t, arg2: *const libc::c_char, arg3: usize) -> libc::c_int; +} +extern "C" { + pub fn wcstombs(arg1: *mut libc::c_char, arg2: *const wchar_t, arg3: usize) -> usize; +} +extern "C" { + pub fn alloca(arg1: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn dlmallinfo() -> mallinfo; +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct mallinfo { + pub arena: libc::c_int, + pub ordblks: libc::c_int, + pub smblks: libc::c_int, + pub hblks: libc::c_int, + pub hblkhd: libc::c_int, + pub usmblks: libc::c_int, + pub fsmblks: libc::c_int, + pub uordblks: libc::c_int, + pub fordblks: libc::c_int, + pub keepcost: libc::c_int, +} +#[test] +fn bindgen_test_layout_mallinfo() { + assert_eq!( + ::core::mem::size_of::(), + 40usize, + concat!("Size of: ", stringify!(mallinfo)) + ); + assert_eq!( + ::core::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(mallinfo)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).arena as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(mallinfo), + "::", + stringify!(arena) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).ordblks as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(mallinfo), + "::", + stringify!(ordblks) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).smblks as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(mallinfo), + "::", + stringify!(smblks) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).hblks as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(mallinfo), + "::", + stringify!(hblks) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).hblkhd as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(mallinfo), + "::", + stringify!(hblkhd) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).usmblks as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(mallinfo), + "::", + stringify!(usmblks) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).fsmblks as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(mallinfo), + "::", + stringify!(fsmblks) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).uordblks as *const _ as usize }, + 28usize, + concat!( + "Offset of field: ", + stringify!(mallinfo), + "::", + stringify!(uordblks) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).fordblks as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(mallinfo), + "::", + stringify!(fordblks) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).keepcost as *const _ as usize }, + 36usize, + concat!( + "Offset of field: ", + stringify!(mallinfo), + "::", + stringify!(keepcost) + ) + ); +} +pub type __builtin_va_list = [__va_list_tag; 1usize]; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct __va_list_tag { + pub gp_offset: libc::c_uint, + pub fp_offset: libc::c_uint, + pub overflow_arg_area: *mut libc::c_void, + pub reg_save_area: *mut libc::c_void, +} +#[test] +fn bindgen_test_layout___va_list_tag() { + assert_eq!( + ::core::mem::size_of::<__va_list_tag>(), + 24usize, + concat!("Size of: ", stringify!(__va_list_tag)) + ); + assert_eq!( + ::core::mem::align_of::<__va_list_tag>(), + 8usize, + concat!("Alignment of ", stringify!(__va_list_tag)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__va_list_tag>())).gp_offset as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(gp_offset) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__va_list_tag>())).fp_offset as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(fp_offset) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<__va_list_tag>())).overflow_arg_area as *const _ as usize + }, + 8usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(overflow_arg_area) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__va_list_tag>())).reg_save_area as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(reg_save_area) + ) + ); +} +impl Default for __va_list_tag { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} diff --git a/enclave/sgx_ffi/src/lib.rs b/enclave/sgx_ffi/src/lib.rs new file mode 100644 index 0000000..383b8a9 --- /dev/null +++ b/enclave/sgx_ffi/src/lib.rs @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![cfg_attr(not(any(test, feature = "test")), no_std)] +#![allow( + unused_parens, + clippy::style, + clippy::large_enum_variant, +)] +#![warn( + bare_trait_objects, + elided_lifetimes_in_paths, + trivial_numeric_casts, + variant_size_differences, + clippy::integer_arithmetic, +)] +#![deny( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::cast_sign_loss, + clippy::clone_on_ref_ptr, + clippy::expl_impl_clone_on_copy, + clippy::explicit_into_iter_loop, + clippy::explicit_iter_loop, + clippy::float_arithmetic, + clippy::float_cmp_const, + clippy::indexing_slicing, + clippy::maybe_infinite_iter, + clippy::mem_forget, + clippy::missing_const_for_fn, + clippy::multiple_inherent_impl, + clippy::mut_mut, + clippy::needless_borrow, + clippy::option_unwrap_used, + clippy::panicking_unwrap, + clippy::print_stdout, + clippy::redundant_clone, + clippy::replace_consts, + clippy::result_unwrap_used, + clippy::shadow_unrelated, + clippy::unimplemented, + clippy::use_debug, + clippy::use_self, + clippy::use_underscore_binding, + clippy::wildcard_enum_match_arm, +)] + +extern crate alloc; + +#[allow(dead_code, non_camel_case_types, non_upper_case_globals, non_snake_case, improper_ctypes, clippy::all, clippy::pedantic, clippy::integer_arithmetic)] +mod bindgen_wrapper; +pub mod sgx; +pub mod untrusted_slice; +pub mod util; + +#[cfg(any(test, feature = "test"))] pub mod mocks; diff --git a/enclave/sgx_ffi/src/mocks.rs b/enclave/sgx_ffi/src/mocks.rs new file mode 100644 index 0000000..48b246d --- /dev/null +++ b/enclave/sgx_ffi/src/mocks.rs @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(clippy::all, clippy::option_unwrap_used, clippy::cast_sign_loss, clippy::cast_possible_truncation)] + +use std::cell::{RefCell}; + +use mockers::*; +use mockers::matchers::*; +use mockers_derive::mocked; +use test_ffi::*; + +use super::bindgen_wrapper::{ + errno_t, + sgx_attributes_t, + sgx_cpu_svn_t, + sgx_key_id_t, + sgx_measurement_t, + sgx_target_info_t, + sgx_report_body_t, + sgx_report_data_t, + sgx_report_t, + sgx_status_t, +}; + +// +// mock extern "C" functions +// + +thread_local! { + pub static SGX_IS_OUTSIDE_ENCLAVE: RefCell> = RefCell::new(None); +} + +#[mocked] +pub trait SgxIsOutsideEnclave { + fn sgx_is_outside_enclave(&self, addr: *const ::std::os::raw::c_void, size: usize) -> bool; +} + +pub fn expect_sgx_is_outside_enclave(scenario: &Scenario, + ptr: *const libc::c_void, + size: usize, + res: bool) { + let mock = mock_for(&SGX_IS_OUTSIDE_ENCLAVE, scenario); + scenario.expect(mock.sgx_is_outside_enclave( + eq(ptr as *const libc::c_void), + eq(size) + ).and_return(res)); +} + +// +// mock extern "C" function implementations +// + +pub mod impls { + use super::*; + + #[no_mangle] + pub extern "C" fn sgx_is_outside_enclave( + addr: *const ::std::os::raw::c_void, + size: usize, + ) -> ::std::os::raw::c_int { + let res = SGX_IS_OUTSIDE_ENCLAVE.with(|mock| { + (mock.borrow().as_ref().expect("no mock for sgx_is_outside_enclave")) + .sgx_is_outside_enclave(addr, size) + }); + res as i32 + } + + #[no_mangle] + pub extern "C" fn sgx_create_report( + target_info: *const sgx_target_info_t, + report_data: *const sgx_report_data_t, + report: *mut sgx_report_t, + ) -> sgx_status_t { + if !target_info.is_null() { + unsafe { std::ptr::read_volatile(target_info) }; + } + let mut config_id = [0; 64]; + let mut reserved4 = [0; 42]; + read_rand(&mut config_id); + read_rand(&mut reserved4); + unsafe { *report = sgx_report_t { + body: sgx_report_body_t { + cpu_svn: sgx_cpu_svn_t { svn: rand() }, + misc_select: rand(), + reserved1: rand(), + isv_ext_prod_id: rand(), + attributes: sgx_attributes_t { + flags: rand(), + xfrm: rand(), + }, + mr_enclave: sgx_measurement_t { m: rand() }, + reserved2: rand(), + mr_signer: sgx_measurement_t { m: rand() }, + reserved3: rand(), + config_id, + isv_prod_id: rand(), + isv_svn: rand(), + config_svn: rand(), + reserved4, + isv_family_id: rand(), + report_data: *report_data, + }, + key_id: sgx_key_id_t { id: rand() }, + mac: rand(), + }}; + 0 + } + + #[no_mangle] + pub extern "C" fn memset_s(s: *mut ::std::os::raw::c_void, + smax: usize, + c: ::std::os::raw::c_int, + n: usize) + -> errno_t + { + assert!(c >= 0); + assert!(n.checked_add(c as usize).unwrap() <= smax); + assert!(!s.is_null()); + 0 + } + + #[no_mangle] + pub extern "C" fn consttime_memequal(b1: *const ::std::os::raw::c_void, + b2: *const ::std::os::raw::c_void, + len: usize) + -> ::std::os::raw::c_int + { + unsafe { + let b1 = std::slice::from_raw_parts(b1 as *const u8, len); + let b2 = std::slice::from_raw_parts(b2 as *const u8, len); + (b1 == b2) as i32 + } + } +} diff --git a/enclave/sgx_ffi/src/sgx.rs b/enclave/sgx_ffi/src/sgx.rs new file mode 100644 index 0000000..56708b5 --- /dev/null +++ b/enclave/sgx_ffi/src/sgx.rs @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use alloc::vec::{Vec}; +use core::slice; +use core::mem; +use core::ptr; + +use super::bindgen_wrapper::{ + sgx_create_report, + sgx_attributes_t, + sgx_measurement_t, + sgx_report_data_t, + sgx_target_info_t, +}; +pub use super::bindgen_wrapper::{ + sgx_status_t as SgxStatus, + sgx_report_t as SgxReport, + SGX_SUCCESS, + SGX_ERROR_INVALID_PARAMETER, + SGX_ERROR_INVALID_STATE, + SGX_ERROR_UNEXPECTED, +}; + +pub struct SgxTargetInfo<'a> { + pub mrenclave: &'a [u8], + pub flags: u64, + pub xfrm: u64, + pub misc_select: u32, + pub config_svn: u16, + pub config_id: &'a [u8], +} + +pub fn create_report(qe_target_info: &SgxTargetInfo<'_>, + report_data_in: &[u8]) + -> Result, SgxStatus> { + let mut sgx_qe_target_info = sgx_target_info_t { + mr_enclave: sgx_measurement_t { + m: [0; 32], + }, + attributes: sgx_attributes_t { + flags: qe_target_info.flags, + xfrm: qe_target_info.xfrm, + }, + reserved1: [0; 2], + config_svn: qe_target_info.config_svn, + misc_select: qe_target_info.misc_select, + reserved2: [0; 8], + config_id: [0; 64], + reserved3: [0; 384], + }; + if qe_target_info.mrenclave.len() != sgx_qe_target_info.mr_enclave.m.len() { + return Err(SGX_ERROR_INVALID_PARAMETER); + } + if qe_target_info.config_id.len() != sgx_qe_target_info.config_id.len() { + return Err(SGX_ERROR_INVALID_PARAMETER); + } + sgx_qe_target_info.mr_enclave.m.copy_from_slice(qe_target_info.mrenclave); + sgx_qe_target_info.config_id.copy_from_slice(qe_target_info.config_id); + let report = create_report_raw(Some(&sgx_qe_target_info), report_data_in)?; + let report_ref = &report; + unsafe { + let report_slice = slice::from_raw_parts(report_ref as *const SgxReport as *const u8, + mem::size_of::()); + Ok(report_slice.to_vec()) + } +} + +pub fn create_report_raw(qe_target_info: Option<&sgx_target_info_t>, + report_data_in: &[u8]) + -> Result { + let mut report_data = sgx_report_data_t { d: [0; 64] }; + if let Some(()) = report_data.d.get_mut(..report_data_in.len()).map(|report_data_part| { + report_data_part.copy_from_slice(report_data_in); + }) { + let mut report: SgxReport = unsafe { mem::zeroed() }; + let res = unsafe { + if let Some(qe_target_info) = qe_target_info { + sgx_create_report(qe_target_info, &report_data, &mut report) + } else { + sgx_create_report(ptr::null(), &report_data, &mut report) + } + }; + if res == SGX_SUCCESS { + return Ok(report) + } else { + return Err(res) + } + } else { + return Err(SGX_ERROR_INVALID_PARAMETER); + } +} + +#[cfg(test)] +pub mod tests { + use test_ffi::{rand_bytes}; + + use super::*; + + fn target_info<'a>(mrenclave: &'a [u8], config_id: &'a [u8]) -> SgxTargetInfo<'a> { + SgxTargetInfo { + mrenclave, + flags: Default::default(), + xfrm: Default::default(), + misc_select: Default::default(), + config_svn: Default::default(), + config_id, + } + } + + #[test] + fn create_report_bad_args() { + let dummy_target_info = sgx_target_info_t::default(); + + let qe_mrenclave = rand_bytes(vec![0; std::mem::size_of_val(&dummy_target_info.mr_enclave)]); + let qe_config_id = rand_bytes(vec![0; std::mem::size_of_val(&dummy_target_info.config_id)]); + let qe_target_info = target_info(&qe_mrenclave, &qe_config_id); + let bad_report_data = rand_bytes(vec![0; std::mem::size_of::() + 1]); + + assert_eq!(Err(SGX_ERROR_INVALID_PARAMETER), create_report(&target_info(&[], &qe_config_id), &[])); + assert_eq!(Err(SGX_ERROR_INVALID_PARAMETER), create_report(&target_info(&[0], &qe_config_id), &[])); + assert_eq!(Err(SGX_ERROR_INVALID_PARAMETER), create_report(&target_info(&qe_mrenclave, &[]), &[])); + assert_eq!(Err(SGX_ERROR_INVALID_PARAMETER), create_report(&target_info(&qe_mrenclave, &[0]), &[])); + assert_eq!(Err(SGX_ERROR_INVALID_PARAMETER), create_report(&qe_target_info, &bad_report_data)); + assert!(create_report(&qe_target_info, &[]).is_ok()); + } + + #[test] + fn create_report_valid() { + let dummy_target_info = sgx_target_info_t::default(); + + let qe_mrenclave = rand_bytes(vec![0; std::mem::size_of_val(&dummy_target_info.mr_enclave)]); + let qe_config_id = rand_bytes(vec![0; std::mem::size_of_val(&dummy_target_info.config_id)]); + let qe_target_info = target_info(&qe_mrenclave, &qe_config_id); + let report_data = rand_bytes(vec![0; std::mem::size_of::()]); + + for report_data_len in 0..=report_data.len() { + let res = create_report(&qe_target_info, &report_data[..report_data_len]); + assert!(res.is_ok()); + if let Ok(report_bytes) = res { + let report: SgxReport = unsafe { + std::ptr::read_unaligned(report_bytes.as_ptr() as *const SgxReport) + }; + assert_eq!(&report_data[..report_data_len], &report.body.report_data.d[..report_data_len]); + assert_eq!(&report.body.report_data.d[report_data_len..], + &vec![0; report.body.report_data.d.len() - report_data_len][..]); + } + } + } +} diff --git a/enclave/sgx_ffi/src/untrusted_slice.rs b/enclave/sgx_ffi/src/untrusted_slice.rs new file mode 100644 index 0000000..8821e16 --- /dev/null +++ b/enclave/sgx_ffi/src/untrusted_slice.rs @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use alloc::vec::{Vec}; +use core::marker::*; +use core::num::*; +use core::ptr::*; + +use super::bindgen_wrapper::{ + sgx_is_outside_enclave, +}; + +pub enum UntrustedSlice<'a> { + NonEmpty { + data: NonNull, + size: NonZeroUsize, + + _phantom: &'a PhantomData<()>, + }, + Empty, +} + +// +// UntrustedSlice impls +// + +impl<'a> UntrustedSlice<'a> { + pub fn new(p_data: *mut u8, size: usize) -> Result, ()> { + let maybe_data_and_size = if let Some(size) = NonZeroUsize::new(size) { + if let Some(data) = NonNull::new(p_data) { + Some((data, size)) + } else { + None + } + } else { + None + }; + if let Some((data, size)) = maybe_data_and_size { + if unsafe { sgx_is_outside_enclave(data.as_ptr() as *const libc::c_void, size.get()) } != 1 { + return Err(()); + } + if data.as_ptr().wrapping_add(size.get()) < data.as_ptr() { + return Err(()); + } + Ok(UntrustedSlice::NonEmpty { data, size, _phantom: &PhantomData }) + } else { + Ok(UntrustedSlice::Empty) + } + } + + pub fn len(&self) -> usize { + match self { + UntrustedSlice::NonEmpty { size, .. } => size.get(), + UntrustedSlice::Empty => 0, + } + } + + pub fn as_ptr(&self) -> *const u8 { + match self { + Self::NonEmpty { data, .. } => data.as_ptr(), + Self::Empty => null(), + } + } + + pub fn offset(&self, offset: usize) -> UntrustedSlice<'_> { + match self { + UntrustedSlice::NonEmpty { data, size, _phantom } => { + if let Some(size) = size.get().checked_sub(offset) { + if let Some(size) = NonZeroUsize::new(size) { + let data = unsafe { NonNull::new_unchecked(data.as_ptr().add(offset)) }; + UntrustedSlice::NonEmpty { data, size, _phantom: &PhantomData } + } else { + UntrustedSlice::Empty + } + } else { + UntrustedSlice::Empty + } + } + UntrustedSlice::Empty => UntrustedSlice::Empty, + } + } + + pub fn read_bytes(&self, read_count: usize) -> Result, ()> { + match self { + UntrustedSlice::NonEmpty { data, size, _phantom } => { + if read_count <= size.get() { + let mut dest = Vec::with_capacity(read_count); + unsafe { + data.as_ptr().copy_to_nonoverlapping(dest.as_mut_ptr(), read_count); + dest.set_len(read_count); + }; + Ok(dest) + } else { + Err(()) + } + } + UntrustedSlice::Empty => { + if read_count == 0 { + Ok(Vec::new()) + } else { + Err(()) + } + } + } + } + + pub fn write_bytes(&self, write_bytes: &[u8]) -> Result<(), ()> { + match self { + UntrustedSlice::NonEmpty { data, size, _phantom } => { + if write_bytes.len() <= size.get() { + unsafe { + write_bytes.as_ptr().copy_to(data.as_ptr(), write_bytes.len()); + } + Ok(()) + } else { + Err(()) + } + } + UntrustedSlice::Empty => { + if write_bytes.is_empty() { + Ok(()) + } else { + Err(()) + } + } + } + } +} + +impl<'a> Default for UntrustedSlice<'a> { + fn default() -> Self { + UntrustedSlice::Empty + } +} + +#[cfg(test)] +mod test { + use mockers::*; + use test_ffi::{rand_bytes}; + + use super::*; + use super::super::mocks; + + struct TestVec { + ptr: *mut u8, + size: usize, + } + impl TestVec { + fn new(size: usize) -> Self { + let mut data_vec: Vec = rand_bytes(vec![0; size]); + let ptr: *mut u8 = data_vec.as_mut_ptr(); + let size: usize = data_vec.capacity(); + std::mem::forget(data_vec); + Self { ptr, size } + } + } + impl Drop for TestVec { + fn drop(&mut self) { + unsafe { Vec::from_raw_parts(self.ptr, self.size, self.size) }; + } + } + + #[test] + fn test_new_valid_empty() { + assert_eq!(UntrustedSlice::new(std::ptr::null_mut(), 0).unwrap().len(), 0); + assert_eq!(UntrustedSlice::new(std::ptr::null_mut(), 1).unwrap().len(), 0); + assert_eq!(UntrustedSlice::new(std::ptr::null_mut(), usize::max_value()).unwrap().len(), 0); + assert_eq!(UntrustedSlice::new(std::ptr::NonNull::dangling().as_ptr(), 0).unwrap().len(), 0); + } + + #[test] + fn test_new_invalid() { + let scenario = Scenario::new(); + let test_vec = TestVec::new(1); + + mocks::expect_sgx_is_outside_enclave(&scenario, test_vec.ptr as *const libc::c_void, usize::max_value(), true); + assert!(UntrustedSlice::new(test_vec.ptr, usize::max_value()).is_err()); + + mocks::expect_sgx_is_outside_enclave(&scenario, test_vec.ptr as *const libc::c_void, test_vec.size, false); + assert!(UntrustedSlice::new(test_vec.ptr, test_vec.size).is_err()); + } + + #[test] + fn test_offset() { + let scenario = Scenario::new(); + let test_vec = TestVec::new(10); + + mocks::expect_sgx_is_outside_enclave(&scenario, test_vec.ptr as *const libc::c_void, test_vec.size, true); + let untrusted = UntrustedSlice::new(test_vec.ptr, test_vec.size).unwrap(); + + assert_eq!(untrusted.len(), test_vec.size); + + for offset in 0..test_vec.size { + assert_eq!(untrusted.offset(offset).len(), test_vec.size - offset); + } + + assert_eq!(untrusted.offset(test_vec.size + 1).len(), 0); + assert_eq!(untrusted.offset(usize::max_value()).len(), 0); + } + + #[test] + fn test_read_write_bytes() { + let scenario = Scenario::new(); + let test_vec = TestVec::new(10); + + mocks::expect_sgx_is_outside_enclave(&scenario, test_vec.ptr as *const libc::c_void, test_vec.size, true); + let untrusted = UntrustedSlice::new(test_vec.ptr, test_vec.size).unwrap(); + + let write_data = rand_bytes(vec![0; test_vec.size]); + assert!(untrusted.write_bytes(&write_data).is_ok()); + assert_eq!(&untrusted.read_bytes(test_vec.size).unwrap(), &write_data); + + for offset in 0..test_vec.size { + for length in 0..=(test_vec.size - offset) { + let write_data = rand_bytes(vec![0; length]); + assert!(untrusted.offset(offset).write_bytes(&write_data).is_ok()); + assert_eq!(&untrusted.offset(offset).read_bytes(length).unwrap(), &write_data); + } + assert!(untrusted.offset(offset).write_bytes(&vec![0; test_vec.size - offset + 1]).is_err()); + assert!(untrusted.offset(offset).read_bytes(test_vec.size - offset + 1).is_err()); + assert!(untrusted.offset(offset).read_bytes(usize::max_value()).is_err()); + } + + assert!(untrusted.offset(test_vec.size).write_bytes(&[]).is_ok()); + assert!(untrusted.offset(test_vec.size).read_bytes(0).unwrap().is_empty()); + assert!(untrusted.offset(test_vec.size + 1).write_bytes(&[]).is_ok()); + assert!(untrusted.offset(test_vec.size + 1).read_bytes(0).unwrap().is_empty()); + assert!(untrusted.offset(usize::max_value()).write_bytes(&[]).is_ok()); + assert!(untrusted.offset(usize::max_value()).read_bytes(0).unwrap().is_empty()); + + assert!(untrusted.offset(test_vec.size).read_bytes(1).is_err()); + assert!(untrusted.offset(test_vec.size).write_bytes(&[0]).is_err()); + assert!(untrusted.offset(test_vec.size).read_bytes(usize::max_value()).is_err()); + assert!(untrusted.offset(usize::max_value()).read_bytes(1).is_err()); + assert!(untrusted.offset(usize::max_value()).write_bytes(&[0]).is_err()); + assert!(untrusted.offset(usize::max_value()).read_bytes(usize::max_value()).is_err()); + } +} diff --git a/enclave/sgx_ffi/src/util.rs b/enclave/sgx_ffi/src/util.rs new file mode 100644 index 0000000..5594140 --- /dev/null +++ b/enclave/sgx_ffi/src/util.rs @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use core::ffi::{c_void}; +use core::mem; + +use super::bindgen_wrapper::{dlmallinfo}; + +pub use super::bindgen_wrapper::{memset_s, consttime_memequal}; + +pub fn clear(buf: &mut [u8]) { + let res = unsafe { memset_s(buf.as_ptr() as *mut c_void, buf.len(), 0, buf.len()) }; + assert_eq!(res, 0); +} + +#[derive(Default)] +pub struct SecretValue>(T); + +pub struct MemoryStatus { + pub footprint_bytes: u32, + pub used_bytes: u32, + pub free_chunks: u32, +} + +pub trait ToUsize { + fn to_usize(self) -> usize; +} + +pub trait ToU64 { + fn to_u64(self) -> u64; +} + +// +// SecretValue impls +// + +impl> SecretValue { + pub fn new(value: T) -> Self { + Self(value) + } + + pub fn clear(&mut self) { + clear(self.0.as_mut()); + } + + pub fn clear_to(&mut self, len: usize) { + if let Some(data) = self.0.as_mut().get_mut(..len) { + clear(data); + } else { + self.clear(); + } + } + + pub fn get(&self) -> &T { + &self.0 + } + + pub fn get_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl + AsMut<[u8]>> SecretValue { + pub fn consttime_eq(&self, other: &[u8]) -> bool { + let ours = self.0.as_ref(); + if other.len() == ours.len() { + let res = unsafe { + consttime_memequal(ours.as_ptr() as *const c_void, + other.as_ptr() as *const c_void, + ours.len()) + }; + res != 0 + } else { + false + } + } +} + +impl + Default> SecretValue { + pub fn into_inner(mut self) -> T { + mem::replace(&mut self.0, Default::default()) + } +} + +impl> Drop for SecretValue { + fn drop(&mut self) { + self.clear(); + } +} + +// +// MemoryStatus impls +// + +#[allow(clippy::cast_sign_loss)] +impl MemoryStatus { + pub fn collect() -> Self { + let mallinfo = unsafe { dlmallinfo() }; + Self { + footprint_bytes: mallinfo.arena as u32, + used_bytes: mallinfo.uordblks as u32, + free_chunks: mallinfo.ordblks as u32, + } + } +} + +// +// ToUsize impls +// + +#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] +impl ToUsize for u32 { + fn to_usize(self) -> usize { + self as usize + } +} + +#[allow(clippy::cast_possible_truncation)] +#[cfg(any(target_pointer_width = "64"))] +impl ToUsize for u64 { + fn to_usize(self) -> usize { + self as usize + } +} + +// +// ToU64 impls +// + +#[cfg(any(target_pointer_width = "64"))] +impl ToU64 for usize { + fn to_u64(self) -> u64 { + self as u64 + } +} diff --git a/enclave/sgxsd_enclave/bearssl/aes_x86ni.c b/enclave/sgxsd_enclave/bearssl/aes_x86ni.c new file mode 100644 index 0000000..d5408f1 --- /dev/null +++ b/enclave/sgxsd_enclave/bearssl/aes_x86ni.c @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#define BR_ENABLE_INTRINSICS 1 +#include "inner.h" + +/* + * This code contains the AES key schedule implementation using the + * AES-NI opcodes. + */ + +#if BR_AES_X86NI + +/* see inner.h */ +int +br_aes_x86ni_supported(void) +{ + /* + * Bit mask for features in ECX: + * 19 SSE4.1 (used for _mm_insert_epi32(), for AES-CTR) + * 25 AES-NI + */ + return br_cpuid(0, 0, 0x02080000, 0); +} + +BR_TARGETS_X86_UP + +BR_TARGET("sse2,aes") +static inline __m128i +expand_step128(__m128i k, __m128i k2) +{ + k = _mm_xor_si128(k, _mm_slli_si128(k, 4)); + k = _mm_xor_si128(k, _mm_slli_si128(k, 4)); + k = _mm_xor_si128(k, _mm_slli_si128(k, 4)); + k2 = _mm_shuffle_epi32(k2, 0xFF); + return _mm_xor_si128(k, k2); +} + +BR_TARGET("sse2,aes") +static inline void +expand_step192(__m128i *t1, __m128i *t2, __m128i *t3) +{ + __m128i t4; + + *t2 = _mm_shuffle_epi32(*t2, 0x55); + t4 = _mm_slli_si128(*t1, 0x4); + *t1 = _mm_xor_si128(*t1, t4); + t4 = _mm_slli_si128(t4, 0x4); + *t1 = _mm_xor_si128(*t1, t4); + t4 = _mm_slli_si128(t4, 0x4); + *t1 = _mm_xor_si128(*t1, t4); + *t1 = _mm_xor_si128(*t1, *t2); + *t2 = _mm_shuffle_epi32(*t1, 0xFF); + t4 = _mm_slli_si128(*t3, 0x4); + *t3 = _mm_xor_si128(*t3, t4); + *t3 = _mm_xor_si128(*t3, *t2); +} + +BR_TARGET("sse2,aes") +static inline void +expand_step256_1(__m128i *t1, __m128i *t2) +{ + __m128i t4; + + *t2 = _mm_shuffle_epi32(*t2, 0xFF); + t4 = _mm_slli_si128(*t1, 0x4); + *t1 = _mm_xor_si128(*t1, t4); + t4 = _mm_slli_si128(t4, 0x4); + *t1 = _mm_xor_si128(*t1, t4); + t4 = _mm_slli_si128(t4, 0x4); + *t1 = _mm_xor_si128(*t1, t4); + *t1 = _mm_xor_si128(*t1, *t2); +} + +BR_TARGET("sse2,aes") +static inline void +expand_step256_2(__m128i *t1, __m128i *t3) +{ + __m128i t2, t4; + + t4 = _mm_aeskeygenassist_si128(*t1, 0x0); + t2 = _mm_shuffle_epi32(t4, 0xAA); + t4 = _mm_slli_si128(*t3, 0x4); + *t3 = _mm_xor_si128(*t3, t4); + t4 = _mm_slli_si128(t4, 0x4); + *t3 = _mm_xor_si128(*t3, t4); + t4 = _mm_slli_si128(t4, 0x4); + *t3 = _mm_xor_si128(*t3, t4); + *t3 = _mm_xor_si128(*t3, t2); +} + +/* + * Perform key schedule for AES, encryption direction. Subkeys are written + * in sk[], and the number of rounds is returned. Key length MUST be 16, + * 24 or 32 bytes. + */ +BR_TARGET("sse2,aes") +static unsigned +x86ni_keysched(__m128i *sk, const void *key, size_t len) +{ + const unsigned char *kb; + +#define KEXP128(k, i, rcon) do { \ + k = expand_step128(k, _mm_aeskeygenassist_si128(k, rcon)); \ + sk[i] = k; \ + } while (0) + +#define KEXP192(i, rcon1, rcon2) do { \ + sk[(i) + 0] = t1; \ + sk[(i) + 1] = t3; \ + t2 = _mm_aeskeygenassist_si128(t3, rcon1); \ + expand_step192(&t1, &t2, &t3); \ + sk[(i) + 1] = _mm_castpd_si128(_mm_shuffle_pd( \ + _mm_castsi128_pd(sk[(i) + 1]), \ + _mm_castsi128_pd(t1), 0)); \ + sk[(i) + 2] = _mm_castpd_si128(_mm_shuffle_pd( \ + _mm_castsi128_pd(t1), \ + _mm_castsi128_pd(t3), 1)); \ + t2 = _mm_aeskeygenassist_si128(t3, rcon2); \ + expand_step192(&t1, &t2, &t3); \ + } while (0) + +#define KEXP256(i, rcon) do { \ + sk[(i) + 0] = t3; \ + t2 = _mm_aeskeygenassist_si128(t3, rcon); \ + expand_step256_1(&t1, &t2); \ + sk[(i) + 1] = t1; \ + expand_step256_2(&t1, &t3); \ + } while (0) + + kb = key; + switch (len) { + __m128i t1, t2, t3; + + case 16: + t1 = _mm_loadu_si128((const void *)kb); + sk[0] = t1; + KEXP128(t1, 1, 0x01); + KEXP128(t1, 2, 0x02); + KEXP128(t1, 3, 0x04); + KEXP128(t1, 4, 0x08); + KEXP128(t1, 5, 0x10); + KEXP128(t1, 6, 0x20); + KEXP128(t1, 7, 0x40); + KEXP128(t1, 8, 0x80); + KEXP128(t1, 9, 0x1B); + KEXP128(t1, 10, 0x36); + return 10; + + case 24: + t1 = _mm_loadu_si128((const void *)kb); + t3 = _mm_loadu_si128((const void *)(kb + 8)); + t3 = _mm_shuffle_epi32(t3, 0x4E); + KEXP192(0, 0x01, 0x02); + KEXP192(3, 0x04, 0x08); + KEXP192(6, 0x10, 0x20); + KEXP192(9, 0x40, 0x80); + sk[12] = t1; + return 12; + + case 32: + t1 = _mm_loadu_si128((const void *)kb); + t3 = _mm_loadu_si128((const void *)(kb + 16)); + sk[0] = t1; + KEXP256( 1, 0x01); + KEXP256( 3, 0x02); + KEXP256( 5, 0x04); + KEXP256( 7, 0x08); + KEXP256( 9, 0x10); + KEXP256(11, 0x20); + sk[13] = t3; + t2 = _mm_aeskeygenassist_si128(t3, 0x40); + expand_step256_1(&t1, &t2); + sk[14] = t1; + return 14; + + default: + return 0; + } + +#undef KEXP128 +#undef KEXP192 +#undef KEXP256 +} + +/* see inner.h */ +BR_TARGET("sse2,aes") +unsigned +br_aes_x86ni_keysched_enc(unsigned char *skni, const void *key, size_t len) +{ + __m128i sk[15]; + unsigned num_rounds; + + num_rounds = x86ni_keysched(sk, key, len); + memcpy(skni, sk, (num_rounds + 1) << 4); + return num_rounds; +} + +/* see inner.h */ +BR_TARGET("sse2,aes") +unsigned +br_aes_x86ni_keysched_dec(unsigned char *skni, const void *key, size_t len) +{ + __m128i sk[15]; + unsigned u, num_rounds; + + num_rounds = x86ni_keysched(sk, key, len); + _mm_storeu_si128((void *)skni, sk[num_rounds]); + for (u = 1; u < num_rounds; u ++) { + _mm_storeu_si128((void *)(skni + (u << 4)), + _mm_aesimc_si128(sk[num_rounds - u])); + } + _mm_storeu_si128((void *)(skni + (num_rounds << 4)), sk[0]); + return num_rounds; +} + +BR_TARGETS_X86_DOWN + +#endif diff --git a/enclave/sgxsd_enclave/bearssl/aes_x86ni_ctr.c b/enclave/sgxsd_enclave/bearssl/aes_x86ni_ctr.c new file mode 100644 index 0000000..1cddd60 --- /dev/null +++ b/enclave/sgxsd_enclave/bearssl/aes_x86ni_ctr.c @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#define BR_ENABLE_INTRINSICS 1 +#include "inner.h" + +#if BR_AES_X86NI + +/* see bearssl_block.h */ +const br_block_ctr_class * +br_aes_x86ni_ctr_get_vtable(void) +{ + return br_aes_x86ni_supported() ? &br_aes_x86ni_ctr_vtable : NULL; +} + +/* see bearssl_block.h */ +void +br_aes_x86ni_ctr_init(br_aes_x86ni_ctr_keys *ctx, + const void *key, size_t len) +{ + ctx->vtable = &br_aes_x86ni_ctr_vtable; + ctx->num_rounds = br_aes_x86ni_keysched_enc(ctx->skey.skni, key, len); +} + +BR_TARGETS_X86_UP + +/* see bearssl_block.h */ +BR_TARGET("sse2,sse4.1,aes") +uint32_t +br_aes_x86ni_ctr_run(const br_aes_x86ni_ctr_keys *ctx, + const void *iv, uint32_t cc, void *data, size_t len) +{ + unsigned char *buf; + unsigned char ivbuf[16]; + unsigned num_rounds; + __m128i sk[15]; + __m128i ivx; + unsigned u; + + buf = data; + memcpy(ivbuf, iv, 12); + num_rounds = ctx->num_rounds; + for (u = 0; u <= num_rounds; u ++) { + sk[u] = _mm_loadu_si128((void *)(ctx->skey.skni + (u << 4))); + } + ivx = _mm_loadu_si128((void *)ivbuf); + while (len > 0) { + __m128i x0, x1, x2, x3; + + x0 = _mm_insert_epi32(ivx, br_bswap32(cc + 0), 3); + x1 = _mm_insert_epi32(ivx, br_bswap32(cc + 1), 3); + x2 = _mm_insert_epi32(ivx, br_bswap32(cc + 2), 3); + x3 = _mm_insert_epi32(ivx, br_bswap32(cc + 3), 3); + x0 = _mm_xor_si128(x0, sk[0]); + x1 = _mm_xor_si128(x1, sk[0]); + x2 = _mm_xor_si128(x2, sk[0]); + x3 = _mm_xor_si128(x3, sk[0]); + x0 = _mm_aesenc_si128(x0, sk[1]); + x1 = _mm_aesenc_si128(x1, sk[1]); + x2 = _mm_aesenc_si128(x2, sk[1]); + x3 = _mm_aesenc_si128(x3, sk[1]); + x0 = _mm_aesenc_si128(x0, sk[2]); + x1 = _mm_aesenc_si128(x1, sk[2]); + x2 = _mm_aesenc_si128(x2, sk[2]); + x3 = _mm_aesenc_si128(x3, sk[2]); + x0 = _mm_aesenc_si128(x0, sk[3]); + x1 = _mm_aesenc_si128(x1, sk[3]); + x2 = _mm_aesenc_si128(x2, sk[3]); + x3 = _mm_aesenc_si128(x3, sk[3]); + x0 = _mm_aesenc_si128(x0, sk[4]); + x1 = _mm_aesenc_si128(x1, sk[4]); + x2 = _mm_aesenc_si128(x2, sk[4]); + x3 = _mm_aesenc_si128(x3, sk[4]); + x0 = _mm_aesenc_si128(x0, sk[5]); + x1 = _mm_aesenc_si128(x1, sk[5]); + x2 = _mm_aesenc_si128(x2, sk[5]); + x3 = _mm_aesenc_si128(x3, sk[5]); + x0 = _mm_aesenc_si128(x0, sk[6]); + x1 = _mm_aesenc_si128(x1, sk[6]); + x2 = _mm_aesenc_si128(x2, sk[6]); + x3 = _mm_aesenc_si128(x3, sk[6]); + x0 = _mm_aesenc_si128(x0, sk[7]); + x1 = _mm_aesenc_si128(x1, sk[7]); + x2 = _mm_aesenc_si128(x2, sk[7]); + x3 = _mm_aesenc_si128(x3, sk[7]); + x0 = _mm_aesenc_si128(x0, sk[8]); + x1 = _mm_aesenc_si128(x1, sk[8]); + x2 = _mm_aesenc_si128(x2, sk[8]); + x3 = _mm_aesenc_si128(x3, sk[8]); + x0 = _mm_aesenc_si128(x0, sk[9]); + x1 = _mm_aesenc_si128(x1, sk[9]); + x2 = _mm_aesenc_si128(x2, sk[9]); + x3 = _mm_aesenc_si128(x3, sk[9]); + if (num_rounds == 10) { + x0 = _mm_aesenclast_si128(x0, sk[10]); + x1 = _mm_aesenclast_si128(x1, sk[10]); + x2 = _mm_aesenclast_si128(x2, sk[10]); + x3 = _mm_aesenclast_si128(x3, sk[10]); + } else if (num_rounds == 12) { + x0 = _mm_aesenc_si128(x0, sk[10]); + x1 = _mm_aesenc_si128(x1, sk[10]); + x2 = _mm_aesenc_si128(x2, sk[10]); + x3 = _mm_aesenc_si128(x3, sk[10]); + x0 = _mm_aesenc_si128(x0, sk[11]); + x1 = _mm_aesenc_si128(x1, sk[11]); + x2 = _mm_aesenc_si128(x2, sk[11]); + x3 = _mm_aesenc_si128(x3, sk[11]); + x0 = _mm_aesenclast_si128(x0, sk[12]); + x1 = _mm_aesenclast_si128(x1, sk[12]); + x2 = _mm_aesenclast_si128(x2, sk[12]); + x3 = _mm_aesenclast_si128(x3, sk[12]); + } else { + x0 = _mm_aesenc_si128(x0, sk[10]); + x1 = _mm_aesenc_si128(x1, sk[10]); + x2 = _mm_aesenc_si128(x2, sk[10]); + x3 = _mm_aesenc_si128(x3, sk[10]); + x0 = _mm_aesenc_si128(x0, sk[11]); + x1 = _mm_aesenc_si128(x1, sk[11]); + x2 = _mm_aesenc_si128(x2, sk[11]); + x3 = _mm_aesenc_si128(x3, sk[11]); + x0 = _mm_aesenc_si128(x0, sk[12]); + x1 = _mm_aesenc_si128(x1, sk[12]); + x2 = _mm_aesenc_si128(x2, sk[12]); + x3 = _mm_aesenc_si128(x3, sk[12]); + x0 = _mm_aesenc_si128(x0, sk[13]); + x1 = _mm_aesenc_si128(x1, sk[13]); + x2 = _mm_aesenc_si128(x2, sk[13]); + x3 = _mm_aesenc_si128(x3, sk[13]); + x0 = _mm_aesenclast_si128(x0, sk[14]); + x1 = _mm_aesenclast_si128(x1, sk[14]); + x2 = _mm_aesenclast_si128(x2, sk[14]); + x3 = _mm_aesenclast_si128(x3, sk[14]); + } + if (len >= 64) { + x0 = _mm_xor_si128(x0, + _mm_loadu_si128((void *)(buf + 0))); + x1 = _mm_xor_si128(x1, + _mm_loadu_si128((void *)(buf + 16))); + x2 = _mm_xor_si128(x2, + _mm_loadu_si128((void *)(buf + 32))); + x3 = _mm_xor_si128(x3, + _mm_loadu_si128((void *)(buf + 48))); + _mm_storeu_si128((void *)(buf + 0), x0); + _mm_storeu_si128((void *)(buf + 16), x1); + _mm_storeu_si128((void *)(buf + 32), x2); + _mm_storeu_si128((void *)(buf + 48), x3); + buf += 64; + len -= 64; + cc += 4; + } else { + unsigned char tmp[64]; + + _mm_storeu_si128((void *)(tmp + 0), x0); + _mm_storeu_si128((void *)(tmp + 16), x1); + _mm_storeu_si128((void *)(tmp + 32), x2); + _mm_storeu_si128((void *)(tmp + 48), x3); + for (u = 0; u < len; u ++) { + buf[u] ^= tmp[u]; + } + cc += (uint32_t)len >> 4; + break; + } + } + return cc; +} + +BR_TARGETS_X86_DOWN + +/* see bearssl_block.h */ +const br_block_ctr_class br_aes_x86ni_ctr_vtable = { + sizeof(br_aes_x86ni_ctr_keys), + 16, + 4, + (void (*)(const br_block_ctr_class **, const void *, size_t)) + &br_aes_x86ni_ctr_init, + (uint32_t (*)(const br_block_ctr_class *const *, + const void *, uint32_t, void *, size_t)) + &br_aes_x86ni_ctr_run +}; + +#else + +/* see bearssl_block.h */ +const br_block_ctr_class * +br_aes_x86ni_ctr_get_vtable(void) +{ + return NULL; +} + +#endif diff --git a/enclave/sgxsd_enclave/bearssl/config.h b/enclave/sgxsd_enclave/bearssl/config.h new file mode 100644 index 0000000..b99ad50 --- /dev/null +++ b/enclave/sgxsd_enclave/bearssl/config.h @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef CONFIG_H__ +#define CONFIG_H__ + +/* + * This file contains compile-time flags that can override the + * autodetection performed in relevant files. Each flag is a macro; it + * deactivates the feature if defined to 0, activates it if defined to a + * non-zero integer (normally 1). If the macro is not defined, then + * autodetection applies. + */ + +#define BR_CPUID 0 + +/* + * When BR_64 is enabled, 64-bit integer types are assumed to be + * efficient (i.e. the architecture has 64-bit registers and can + * do 64-bit operations as fast as 32-bit operations). + * +#define BR_64 1 + */ + +/* + * When BR_LOMUL is enabled, then multiplications of 32-bit values whose + * result are truncated to the low 32 bits are assumed to be + * substantially more efficient than 32-bit multiplications that yield + * 64-bit results. This is typically the case on low-end ARM Cortex M + * systems (M0, M0+, M1, and arguably M3 and M4 as well). + * +#define BR_LOMUL 1 + */ + +/* + * When BR_SLOW_MUL is enabled, multiplications are assumed to be + * substantially slow with regards to other integer operations, thus + * making it worth to make more operations for a given task if it allows + * using less multiplications. + * +#define BR_SLOW_MUL 1 + */ + +/* + * When BR_SLOW_MUL15 is enabled, short multplications (on 15-bit words) + * are assumed to be substantially slow with regards to other integer + * operations, thus making it worth to make more integer operations if + * it allows using less multiplications. + * +#define BR_SLOW_MUL15 1 + */ + +/* + * When BR_CT_MUL31 is enabled, multiplications of 31-bit values (used + * in the "i31" big integer implementation) use an alternate implementation + * which is slower and larger than the normal multiplication, but should + * ensure constant-time multiplications even on architectures where the + * multiplication opcode takes a variable number of cycles to complete. + * +#define BR_CT_MUL31 1 + */ + +/* + * When BR_CT_MUL15 is enabled, multiplications of 15-bit values (held + * in 32-bit words) use an alternate implementation which is slower and + * larger than the normal multiplication, but should ensure + * constant-time multiplications on most/all architectures where the + * basic multiplication is not constant-time. +#define BR_CT_MUL15 1 + */ + +/* + * When BR_NO_ARITH_SHIFT is enabled, arithmetic right shifts (with sign + * extension) are performed with a sequence of operations which is bigger + * and slower than a simple right shift on a signed value. This avoids + * relying on an implementation-defined behaviour. However, most if not + * all C compilers use sign extension for right shifts on signed values, + * so this alternate macro is disabled by default. +#define BR_NO_ARITH_SHIFT 1 + */ + +/* + * When BR_RDRAND is enabled, the SSL engine will use the RDRAND opcode + * to automatically obtain quality randomness for seeding its internal + * PRNG. Since that opcode is present only in recent x86 CPU, its + * support is dynamically tested; if the current CPU does not support + * it, then another random source will be used, such as /dev/urandom or + * CryptGenRandom(). + * +#define BR_RDRAND 1 + */ + +/* + * When BR_USE_GETENTROPY is enabled, the SSL engine will use the + * getentropy() function to obtain quality randomness for seeding its + * internal PRNG. On Linux and FreeBSD, getentropy() is implemented by + * the standard library with the system call getrandom(); on OpenBSD, + * getentropy() is the system call, and there is no getrandom() wrapper, + * hence the use of the getentropy() function for maximum portability. + * + * If the getentropy() call fails, and BR_USE_URANDOM is not explicitly + * disabled, then /dev/urandom will be used as a fallback mechanism. On + * FreeBSD and OpenBSD, this does not change much, since /dev/urandom + * will block if not enough entropy has been obtained since last boot. + * On Linux, /dev/urandom might not block, which can be troublesome in + * early boot stages, which is why getentropy() is preferred. + * +#define BR_USE_GETENTROPY 1 + */ + +/* + * When BR_USE_URANDOM is enabled, the SSL engine will use /dev/urandom + * to automatically obtain quality randomness for seeding its internal + * PRNG. + * +#define BR_USE_URANDOM 1 + */ + +/* + * When BR_USE_WIN32_RAND is enabled, the SSL engine will use the Win32 + * (CryptoAPI) functions (CryptAcquireContext(), CryptGenRandom()...) to + * automatically obtain quality randomness for seeding its internal PRNG. + * + * Note: if both BR_USE_URANDOM and BR_USE_WIN32_RAND are defined, the + * former takes precedence. + * +#define BR_USE_WIN32_RAND 1 + */ + +/* + * When BR_USE_UNIX_TIME is enabled, the X.509 validation engine obtains + * the current time from the OS by calling time(), and assuming that the + * returned value (a 'time_t') is an integer that counts time in seconds + * since the Unix Epoch (Jan 1st, 1970, 00:00 UTC). + * +#define BR_USE_UNIX_TIME 1 + */ + +/* + * When BR_USE_WIN32_TIME is enabled, the X.509 validation engine obtains + * the current time from the OS by calling the Win32 function + * GetSystemTimeAsFileTime(). + * + * Note: if both BR_USE_UNIX_TIME and BR_USE_WIN32_TIME are defined, the + * former takes precedence. + * +#define BR_USE_WIN32_TIME 1 + */ + +/* + * When BR_ARMEL_CORTEXM_GCC is enabled, some operations are replaced with + * inline assembly which is shorter and/or faster. This should be used + * only when all of the following are true: + * - target architecture is ARM in Thumb mode + * - target endianness is little-endian + * - compiler is GCC (or GCC-compatible for inline assembly syntax) + * + * This is meant for the low-end cores (Cortex M0, M0+, M1, M3). + * Note: if BR_LOMUL is not explicitly enabled or disabled, then + * enabling BR_ARMEL_CORTEXM_GCC also enables BR_LOMUL. + * +#define BR_ARMEL_CORTEXM_GCC 1 + */ + +/* + * When BR_AES_X86NI is enabled, the AES implementation using the x86 "NI" + * instructions (dedicated AES opcodes) will be compiled. If this is not + * enabled explicitly, then that AES implementation will be compiled only + * if a compatible compiler is detected. If set explicitly to 0, the + * implementation will not be compiled at all. + * +#define BR_AES_X86NI 1 + */ + +/* + * When BR_SSE2 is enabled, SSE2 intrinsics will be used for some + * algorithm implementations that use them (e.g. chacha20_sse2). If this + * is not enabled explicitly, then support for SSE2 intrinsics will be + * automatically detected. If set explicitly to 0, then SSE2 code will + * not be compiled at all. + * +#define BR_SSE2 1 + */ + +/* + * When BR_POWER8 is enabled, the AES implementation using the POWER ISA + * 2.07 opcodes (available on POWER8 processors and later) is compiled. + * If this is not enabled explicitly, then that implementation will be + * compiled only if a compatible compiler is detected, _and_ the target + * architecture is POWER8 or later. + * +#define BR_POWER8 1 + */ + +/* + * When BR_INT128 is enabled, then code using the 'unsigned __int64' + * and 'unsigned __int128' types will be used to leverage 64x64->128 + * unsigned multiplications. This should work with GCC and compatible + * compilers on 64-bit architectures. + * +#define BR_INT128 1 + */ + +/* + * When BR_UMUL128 is enabled, then code using the '_umul128()' and + * '_addcarry_u64()' intrinsics will be used to implement 64x64->128 + * unsigned multiplications. This should work on Visual C on x64 systems. + * +#define BR_UMUL128 1 + */ + +/* + * When BR_LE_UNALIGNED is enabled, then the current architecture is + * assumed to use little-endian encoding for integers, and to tolerate + * unaligned accesses with no or minimal time penalty. + * +#define BR_LE_UNALIGNED 1 + */ + +/* + * When BR_BE_UNALIGNED is enabled, then the current architecture is + * assumed to use big-endian encoding for integers, and to tolerate + * unaligned accesses with no or minimal time penalty. + * +#define BR_BE_UNALIGNED 1 + */ + +#endif diff --git a/enclave/sgxsd_enclave/bearssl/dec32be.c b/enclave/sgxsd_enclave/bearssl/dec32be.c new file mode 100644 index 0000000..5a8fc59 --- /dev/null +++ b/enclave/sgxsd_enclave/bearssl/dec32be.c @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "inner.h" + +/* see inner.h */ +void +br_range_dec32be(uint32_t *v, size_t num, const void *src) +{ + const unsigned char *buf; + + buf = src; + while (num -- > 0) { + *v ++ = br_dec32be(buf); + buf += 4; + } +} diff --git a/enclave/sgxsd_enclave/bearssl/enc32be.c b/enclave/sgxsd_enclave/bearssl/enc32be.c new file mode 100644 index 0000000..97298b5 --- /dev/null +++ b/enclave/sgxsd_enclave/bearssl/enc32be.c @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "inner.h" + +/* see inner.h */ +void +br_range_enc32be(void *dst, const uint32_t *v, size_t num) +{ + unsigned char *buf; + + buf = dst; + while (num -- > 0) { + br_enc32be(buf, *v ++); + buf += 4; + } +} diff --git a/enclave/sgxsd_enclave/bearssl/gcm.c b/enclave/sgxsd_enclave/bearssl/gcm.c new file mode 100644 index 0000000..ede5f08 --- /dev/null +++ b/enclave/sgxsd_enclave/bearssl/gcm.c @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "inner.h" + +/* + * Implementation Notes + * ==================== + * + * Since CTR and GHASH implementations can handle only full blocks, a + * 16-byte buffer (buf[]) is maintained in the context: + * + * - When processing AAD, buf[] contains the 0-15 unprocessed bytes. + * + * - When doing CTR encryption / decryption, buf[] contains the AES output + * for the last partial block, to be used with the next few bytes of + * data, as well as the already encrypted bytes. For instance, if the + * processed data length so far is 21 bytes, then buf[0..4] contains + * the five last encrypted bytes, and buf[5..15] contains the next 11 + * AES output bytes to be XORed with the next 11 bytes of input. + * + * The recorded AES output bytes are used to complete the block when + * the corresponding bytes are obtained. Note that buf[] always + * contains the _encrypted_ bytes, whether we apply encryption or + * decryption: these bytes are used as input to GHASH when the block + * is complete. + * + * In both cases, the low bits of the data length counters (count_aad, + * count_ctr) are used to work out the current situation. + */ + +/* see bearssl_aead.h */ +void +br_gcm_init(br_gcm_context *ctx, const br_block_ctr_class **bctx, br_ghash gh) +{ + unsigned char iv[12]; + + ctx->vtable = &br_gcm_vtable; + ctx->bctx = bctx; + ctx->gh = gh; + + /* + * The GHASH key h[] is the raw encryption of the all-zero + * block. Since we only have a CTR implementation, we use it + * with an all-zero IV and a zero counter, to CTR-encrypt an + * all-zero block. + */ + memset(ctx->h, 0, sizeof ctx->h); + memset(iv, 0, sizeof iv); + (*bctx)->run(bctx, iv, 0, ctx->h, sizeof ctx->h); +} + +/* see bearssl_aead.h */ +void +br_gcm_reset(br_gcm_context *ctx, const void *iv, size_t len) +{ + /* + * If the provided nonce is 12 bytes, then this is the initial + * IV for CTR mode; it will be used with a counter that starts + * at 2 (value 1 is for encrypting the GHASH output into the tag). + * + * If the provided nonce has any other length, then it is hashed + * (with GHASH) into a 16-byte value that will be the IV for CTR + * (both 12-byte IV and 32-bit counter). + */ + if (len == 12) { + memcpy(ctx->j0_1, iv, 12); + ctx->j0_2 = 1; + } else { + unsigned char ty[16], tmp[16]; + + memset(ty, 0, sizeof ty); + ctx->gh(ty, ctx->h, iv, len); + memset(tmp, 0, 8); + br_enc64be(tmp + 8, (uint64_t)len << 3); + ctx->gh(ty, ctx->h, tmp, 16); + memcpy(ctx->j0_1, ty, 12); + ctx->j0_2 = br_dec32be(ty + 12); + } + ctx->jc = ctx->j0_2 + 1; + memset(ctx->y, 0, sizeof ctx->y); + ctx->count_aad = 0; + ctx->count_ctr = 0; +} + +/* see bearssl_aead.h */ +void +br_gcm_aad_inject(br_gcm_context *ctx, const void *data, size_t len) +{ + size_t ptr, dlen; + + ptr = (size_t)ctx->count_aad & (size_t)15; + if (ptr != 0) { + /* + * If there is a partial block, then we first try to + * complete it. + */ + size_t clen; + + clen = 16 - ptr; + if (len < clen) { + memcpy(ctx->buf + ptr, data, len); + ctx->count_aad += (uint64_t)len; + return; + } + memcpy(ctx->buf + ptr, data, clen); + ctx->gh(ctx->y, ctx->h, ctx->buf, 16); + data = (const unsigned char *)data + clen; + len -= clen; + ctx->count_aad += (uint64_t)clen; + } + + /* + * Now AAD is aligned on a 16-byte block (with regards to GHASH). + * We process all complete blocks, and save the last partial + * block. + */ + dlen = len & ~(size_t)15; + ctx->gh(ctx->y, ctx->h, data, dlen); + memcpy(ctx->buf, (const unsigned char *)data + dlen, len - dlen); + ctx->count_aad += (uint64_t)len; +} + +/* see bearssl_aead.h */ +void +br_gcm_flip(br_gcm_context *ctx) +{ + /* + * We complete the GHASH computation if there is a partial block. + * The GHASH implementation automatically applies padding with + * zeros. + */ + size_t ptr; + + ptr = (size_t)ctx->count_aad & (size_t)15; + if (ptr != 0) { + ctx->gh(ctx->y, ctx->h, ctx->buf, ptr); + } +} + +/* see bearssl_aead.h */ +void +br_gcm_run(br_gcm_context *ctx, int encrypt, void *data, size_t len) +{ + unsigned char *buf; + size_t ptr, dlen; + + buf = data; + ptr = (size_t)ctx->count_ctr & (size_t)15; + if (ptr != 0) { + /* + * If we have a partial block, then we try to complete it. + */ + size_t u, clen; + + clen = 16 - ptr; + if (len < clen) { + clen = len; + } + for (u = 0; u < clen; u ++) { + unsigned x, y; + + x = buf[u]; + y = x ^ ctx->buf[ptr + u]; + ctx->buf[ptr + u] = encrypt ? y : x; + buf[u] = y; + } + ctx->count_ctr += (uint64_t)clen; + buf += clen; + len -= clen; + if (ptr + clen < 16) { + return; + } + ctx->gh(ctx->y, ctx->h, ctx->buf, 16); + } + + /* + * Process full blocks. + */ + dlen = len & ~(size_t)15; + if (!encrypt) { + ctx->gh(ctx->y, ctx->h, buf, dlen); + } + ctx->jc = (*ctx->bctx)->run(ctx->bctx, ctx->j0_1, ctx->jc, buf, dlen); + if (encrypt) { + ctx->gh(ctx->y, ctx->h, buf, dlen); + } + buf += dlen; + len -= dlen; + ctx->count_ctr += (uint64_t)dlen; + + if (len > 0) { + /* + * There is a partial block. + */ + size_t u; + + memset(ctx->buf, 0, sizeof ctx->buf); + ctx->jc = (*ctx->bctx)->run(ctx->bctx, ctx->j0_1, + ctx->jc, ctx->buf, 16); + for (u = 0; u < len; u ++) { + unsigned x, y; + + x = buf[u]; + y = x ^ ctx->buf[u]; + ctx->buf[u] = encrypt ? y : x; + buf[u] = y; + } + ctx->count_ctr += (uint64_t)len; + } +} + +/* see bearssl_aead.h */ +void +br_gcm_get_tag(br_gcm_context *ctx, void *tag) +{ + size_t ptr; + unsigned char tmp[16]; + + ptr = (size_t)ctx->count_ctr & (size_t)15; + if (ptr > 0) { + /* + * There is a partial block: encrypted/decrypted data has + * been produced, but the encrypted bytes must still be + * processed by GHASH. + */ + ctx->gh(ctx->y, ctx->h, ctx->buf, ptr); + } + + /* + * Final block for GHASH: the AAD and plaintext lengths (in bits). + */ + br_enc64be(tmp, ctx->count_aad << 3); + br_enc64be(tmp + 8, ctx->count_ctr << 3); + ctx->gh(ctx->y, ctx->h, tmp, 16); + + /* + * Tag is the GHASH output XORed with the encryption of the + * nonce with the initial counter value. + */ + memcpy(tag, ctx->y, 16); + (*ctx->bctx)->run(ctx->bctx, ctx->j0_1, ctx->j0_2, tag, 16); +} + +/* see bearssl_aead.h */ +void +br_gcm_get_tag_trunc(br_gcm_context *ctx, void *tag, size_t len) +{ + unsigned char tmp[16]; + + br_gcm_get_tag(ctx, tmp); + memcpy(tag, tmp, len); +} + +/* see bearssl_aead.h */ +uint32_t +br_gcm_check_tag_trunc(br_gcm_context *ctx, const void *tag, size_t len) +{ + unsigned char tmp[16]; + size_t u; + int x; + + br_gcm_get_tag(ctx, tmp); + x = 0; + for (u = 0; u < len; u ++) { + x |= tmp[u] ^ ((const unsigned char *)tag)[u]; + } + return EQ0(x); +} + +/* see bearssl_aead.h */ +uint32_t +br_gcm_check_tag(br_gcm_context *ctx, const void *tag) +{ + return br_gcm_check_tag_trunc(ctx, tag, 16); +} + +/* see bearssl_aead.h */ +const br_aead_class br_gcm_vtable = { + 16, + (void (*)(const br_aead_class **, const void *, size_t)) + &br_gcm_reset, + (void (*)(const br_aead_class **, const void *, size_t)) + &br_gcm_aad_inject, + (void (*)(const br_aead_class **)) + &br_gcm_flip, + (void (*)(const br_aead_class **, int, void *, size_t)) + &br_gcm_run, + (void (*)(const br_aead_class **, void *)) + &br_gcm_get_tag, + (uint32_t (*)(const br_aead_class **, const void *)) + &br_gcm_check_tag, + (void (*)(const br_aead_class **, void *, size_t)) + &br_gcm_get_tag_trunc, + (uint32_t (*)(const br_aead_class **, const void *, size_t)) + &br_gcm_check_tag_trunc +}; diff --git a/enclave/sgxsd_enclave/bearssl/ghash_pclmul.c b/enclave/sgxsd_enclave/bearssl/ghash_pclmul.c new file mode 100644 index 0000000..a58e7dc --- /dev/null +++ b/enclave/sgxsd_enclave/bearssl/ghash_pclmul.c @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2017 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#define BR_ENABLE_INTRINSICS 1 +#include "inner.h" + +/* + * This is the GHASH implementation that leverages the pclmulqdq opcode + * (from the AES-NI instructions). + */ + +#if BR_AES_X86NI + +/* + * Test CPU support for PCLMULQDQ. + */ +static inline int +pclmul_supported(void) +{ + /* + * Bit mask for features in ECX: + * 1 PCLMULQDQ support + */ + return br_cpuid(0, 0, 0x00000002, 0); +} + +/* see bearssl_hash.h */ +br_ghash +br_ghash_pclmul_get(void) +{ + return pclmul_supported() ? &br_ghash_pclmul : 0; +} + +BR_TARGETS_X86_UP + +/* + * GHASH is defined over elements of GF(2^128) with "full little-endian" + * representation: leftmost byte is least significant, and, within each + * byte, leftmost _bit_ is least significant. The natural ordering in + * x86 is "mixed little-endian": bytes are ordered from least to most + * significant, but bits within a byte are in most-to-least significant + * order. Going to full little-endian representation would require + * reversing bits within each byte, which is doable but expensive. + * + * Instead, we go to full big-endian representation, by swapping bytes + * around, which is done with a single _mm_shuffle_epi8() opcode (it + * comes with SSSE3; all CPU that offer pclmulqdq also have SSSE3). We + * can use a full big-endian representation because in a carryless + * multiplication, we have a nice bit reversal property: + * + * rev_128(x) * rev_128(y) = rev_255(x * y) + * + * So by using full big-endian, we still get the right result, except + * that it is right-shifted by 1 bit. The left-shift is relatively + * inexpensive, and it can be mutualised. + * + * + * Since SSE2 opcodes do not have facilities for shitfting full 128-bit + * values with bit precision, we have to break down values into 64-bit + * chunks. We number chunks from 0 to 3 in left to right order. + */ + +/* + * Byte-swap a complete 128-bit value. This normally uses + * _mm_shuffle_epi8(), which gets translated to pshufb (an SSSE3 opcode). + * However, this crashes old Clang versions, so, for Clang before 3.8, + * we use an alternate (and less efficient) version. + */ +#if BR_CLANG && !BR_CLANG_3_8 +#define BYTESWAP_DECL +#define BYTESWAP_PREP (void)0 +#define BYTESWAP(x) do { \ + __m128i byteswap1, byteswap2; \ + byteswap1 = (x); \ + byteswap2 = _mm_srli_epi16(byteswap1, 8); \ + byteswap1 = _mm_slli_epi16(byteswap1, 8); \ + byteswap1 = _mm_or_si128(byteswap1, byteswap2); \ + byteswap1 = _mm_shufflelo_epi16(byteswap1, 0x1B); \ + byteswap1 = _mm_shufflehi_epi16(byteswap1, 0x1B); \ + (x) = _mm_shuffle_epi32(byteswap1, 0x4E); \ + } while (0) +#else +#define BYTESWAP_DECL __m128i byteswap_index; +#define BYTESWAP_PREP do { \ + byteswap_index = _mm_set_epi8( \ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); \ + } while (0) +#define BYTESWAP(x) do { \ + (x) = _mm_shuffle_epi8((x), byteswap_index); \ + } while (0) +#endif + +/* + * Call pclmulqdq. Clang appears to have trouble with the intrinsic, so, + * for that compiler, we use inline assembly. Inline assembly is + * potentially a bit slower because the compiler does not understand + * what the opcode does, and thus cannot optimize instruction + * scheduling. + * + * We use a target of "sse2" only, so that Clang may still handle the + * '__m128i' type and allocate SSE2 registers. + */ +#if BR_CLANG +BR_TARGET("sse2") +static inline __m128i +pclmulqdq00(__m128i x, __m128i y) +{ + __asm__ ("pclmulqdq $0x00, %1, %0" : "+x" (x) : "x" (y)); + return x; +} +BR_TARGET("sse2") +static inline __m128i +pclmulqdq11(__m128i x, __m128i y) +{ + __asm__ ("pclmulqdq $0x11, %1, %0" : "+x" (x) : "x" (y)); + return x; +} +#else +#define pclmulqdq00(x, y) _mm_clmulepi64_si128(x, y, 0x00) +#define pclmulqdq11(x, y) _mm_clmulepi64_si128(x, y, 0x11) +#endif + +/* + * From a 128-bit value kw, compute kx as the XOR of the two 64-bit + * halves of kw (into the right half of kx; left half is unspecified). + */ +#define BK(kw, kx) do { \ + kx = _mm_xor_si128(kw, _mm_shuffle_epi32(kw, 0x0E)); \ + } while (0) + +/* + * Combine two 64-bit values (k0:k1) into a 128-bit (kw) value and + * the XOR of the two values (kx). + */ +#define PBK(k0, k1, kw, kx) do { \ + kw = _mm_unpacklo_epi64(k1, k0); \ + kx = _mm_xor_si128(k0, k1); \ + } while (0) + +/* + * Left-shift by 1 bit a 256-bit value (in four 64-bit words). + */ +#define SL_256(x0, x1, x2, x3) do { \ + x0 = _mm_or_si128( \ + _mm_slli_epi64(x0, 1), \ + _mm_srli_epi64(x1, 63)); \ + x1 = _mm_or_si128( \ + _mm_slli_epi64(x1, 1), \ + _mm_srli_epi64(x2, 63)); \ + x2 = _mm_or_si128( \ + _mm_slli_epi64(x2, 1), \ + _mm_srli_epi64(x3, 63)); \ + x3 = _mm_slli_epi64(x3, 1); \ + } while (0) + +/* + * Perform reduction in GF(2^128). The 256-bit value is in x0..x3; + * result is written in x0..x1. + */ +#define REDUCE_F128(x0, x1, x2, x3) do { \ + x1 = _mm_xor_si128( \ + x1, \ + _mm_xor_si128( \ + _mm_xor_si128( \ + x3, \ + _mm_srli_epi64(x3, 1)), \ + _mm_xor_si128( \ + _mm_srli_epi64(x3, 2), \ + _mm_srli_epi64(x3, 7)))); \ + x2 = _mm_xor_si128( \ + _mm_xor_si128( \ + x2, \ + _mm_slli_epi64(x3, 63)), \ + _mm_xor_si128( \ + _mm_slli_epi64(x3, 62), \ + _mm_slli_epi64(x3, 57))); \ + x0 = _mm_xor_si128( \ + x0, \ + _mm_xor_si128( \ + _mm_xor_si128( \ + x2, \ + _mm_srli_epi64(x2, 1)), \ + _mm_xor_si128( \ + _mm_srli_epi64(x2, 2), \ + _mm_srli_epi64(x2, 7)))); \ + x1 = _mm_xor_si128( \ + _mm_xor_si128( \ + x1, \ + _mm_slli_epi64(x2, 63)), \ + _mm_xor_si128( \ + _mm_slli_epi64(x2, 62), \ + _mm_slli_epi64(x2, 57))); \ + } while (0) + +/* + * Square value kw into (dw,dx). + */ +#define SQUARE_F128(kw, dw, dx) do { \ + __m128i z0, z1, z2, z3; \ + z1 = pclmulqdq11(kw, kw); \ + z3 = pclmulqdq00(kw, kw); \ + z0 = _mm_shuffle_epi32(z1, 0x0E); \ + z2 = _mm_shuffle_epi32(z3, 0x0E); \ + SL_256(z0, z1, z2, z3); \ + REDUCE_F128(z0, z1, z2, z3); \ + PBK(z0, z1, dw, dx); \ + } while (0) + +/* see bearssl_hash.h */ +BR_TARGET("ssse3,pclmul") +void +br_ghash_pclmul(void *y, const void *h, const void *data, size_t len) +{ + const unsigned char *buf1, *buf2; + unsigned char tmp[64]; + size_t num4, num1; + __m128i yw, h1w, h1x; + BYTESWAP_DECL + + /* + * We split data into two chunks. First chunk starts at buf1 + * and contains num4 blocks of 64-byte values. Second chunk + * starts at buf2 and contains num1 blocks of 16-byte values. + * We want the first chunk to be as large as possible. + */ + buf1 = data; + num4 = len >> 6; + len &= 63; + buf2 = buf1 + (num4 << 6); + num1 = (len + 15) >> 4; + if ((len & 15) != 0) { + memcpy(tmp, buf2, len); + memset(tmp + len, 0, (num1 << 4) - len); + buf2 = tmp; + } + + /* + * Preparatory step for endian conversions. + */ + BYTESWAP_PREP; + + /* + * Load y and h. + */ + yw = _mm_loadu_si128(y); + h1w = _mm_loadu_si128(h); + BYTESWAP(yw); + BYTESWAP(h1w); + BK(h1w, h1x); + + if (num4 > 0) { + __m128i h2w, h2x, h3w, h3x, h4w, h4x; + __m128i t0, t1, t2, t3; + + /* + * Compute h2 = h^2. + */ + SQUARE_F128(h1w, h2w, h2x); + + /* + * Compute h3 = h^3 = h*(h^2). + */ + t1 = pclmulqdq11(h1w, h2w); + t3 = pclmulqdq00(h1w, h2w); + t2 = _mm_xor_si128(pclmulqdq00(h1x, h2x), + _mm_xor_si128(t1, t3)); + t0 = _mm_shuffle_epi32(t1, 0x0E); + t1 = _mm_xor_si128(t1, _mm_shuffle_epi32(t2, 0x0E)); + t2 = _mm_xor_si128(t2, _mm_shuffle_epi32(t3, 0x0E)); + SL_256(t0, t1, t2, t3); + REDUCE_F128(t0, t1, t2, t3); + PBK(t0, t1, h3w, h3x); + + /* + * Compute h4 = h^4 = (h^2)^2. + */ + SQUARE_F128(h2w, h4w, h4x); + + while (num4 -- > 0) { + __m128i aw0, aw1, aw2, aw3; + __m128i ax0, ax1, ax2, ax3; + + aw0 = _mm_loadu_si128((void *)(buf1 + 0)); + aw1 = _mm_loadu_si128((void *)(buf1 + 16)); + aw2 = _mm_loadu_si128((void *)(buf1 + 32)); + aw3 = _mm_loadu_si128((void *)(buf1 + 48)); + BYTESWAP(aw0); + BYTESWAP(aw1); + BYTESWAP(aw2); + BYTESWAP(aw3); + buf1 += 64; + + aw0 = _mm_xor_si128(aw0, yw); + BK(aw1, ax1); + BK(aw2, ax2); + BK(aw3, ax3); + BK(aw0, ax0); + + t1 = _mm_xor_si128( + _mm_xor_si128( + pclmulqdq11(aw0, h4w), + pclmulqdq11(aw1, h3w)), + _mm_xor_si128( + pclmulqdq11(aw2, h2w), + pclmulqdq11(aw3, h1w))); + t3 = _mm_xor_si128( + _mm_xor_si128( + pclmulqdq00(aw0, h4w), + pclmulqdq00(aw1, h3w)), + _mm_xor_si128( + pclmulqdq00(aw2, h2w), + pclmulqdq00(aw3, h1w))); + t2 = _mm_xor_si128( + _mm_xor_si128( + pclmulqdq00(ax0, h4x), + pclmulqdq00(ax1, h3x)), + _mm_xor_si128( + pclmulqdq00(ax2, h2x), + pclmulqdq00(ax3, h1x))); + t2 = _mm_xor_si128(t2, _mm_xor_si128(t1, t3)); + t0 = _mm_shuffle_epi32(t1, 0x0E); + t1 = _mm_xor_si128(t1, _mm_shuffle_epi32(t2, 0x0E)); + t2 = _mm_xor_si128(t2, _mm_shuffle_epi32(t3, 0x0E)); + SL_256(t0, t1, t2, t3); + REDUCE_F128(t0, t1, t2, t3); + yw = _mm_unpacklo_epi64(t1, t0); + } + } + + while (num1 -- > 0) { + __m128i aw, ax; + __m128i t0, t1, t2, t3; + + aw = _mm_loadu_si128((void *)buf2); + BYTESWAP(aw); + buf2 += 16; + + aw = _mm_xor_si128(aw, yw); + BK(aw, ax); + + t1 = pclmulqdq11(aw, h1w); + t3 = pclmulqdq00(aw, h1w); + t2 = pclmulqdq00(ax, h1x); + t2 = _mm_xor_si128(t2, _mm_xor_si128(t1, t3)); + t0 = _mm_shuffle_epi32(t1, 0x0E); + t1 = _mm_xor_si128(t1, _mm_shuffle_epi32(t2, 0x0E)); + t2 = _mm_xor_si128(t2, _mm_shuffle_epi32(t3, 0x0E)); + SL_256(t0, t1, t2, t3); + REDUCE_F128(t0, t1, t2, t3); + yw = _mm_unpacklo_epi64(t1, t0); + } + + BYTESWAP(yw); + _mm_storeu_si128(y, yw); +} + +BR_TARGETS_X86_DOWN + +#else + +/* see bearssl_hash.h */ +br_ghash +br_ghash_pclmul_get(void) +{ + return 0; +} + +#endif diff --git a/enclave/sgxsd_enclave/bearssl/inner.h b/enclave/sgxsd_enclave/bearssl/inner.h new file mode 100644 index 0000000..65d1e41 --- /dev/null +++ b/enclave/sgxsd_enclave/bearssl/inner.h @@ -0,0 +1,2579 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef INNER_H__ +#define INNER_H__ + +#include +#include + +#include "config.h" +#include "bearssl.h" + +/* + * On MSVC, disable the warning about applying unary minus on an + * unsigned type: it is standard, we do it all the time, and for + * good reasons. + */ +#if _MSC_VER +#pragma warning( disable : 4146 ) +#endif + +/* + * Maximum size for a RSA modulus (in bits). Allocated stack buffers + * depend on that size, so this value should be kept small. Currently, + * 2048-bit RSA keys offer adequate security, and should still do so for + * the next few decades; however, a number of widespread PKI have + * already set their root keys to RSA-4096, so we should be able to + * process such keys. + * + * This value MUST be a multiple of 64. This value MUST NOT exceed 47666 + * (some computations in RSA key generation rely on the factor size being + * no more than 23833 bits). RSA key sizes beyond 3072 bits don't make a + * lot of sense anyway. + */ +#define BR_MAX_RSA_SIZE 4096 + +/* + * Minimum size for a RSA modulus (in bits); this value is used only to + * filter out invalid parameters for key pair generation. Normally, + * applications should not use RSA keys smaller than 2048 bits; but some + * specific cases might need shorter keys, for legacy or research + * purposes. + */ +#define BR_MIN_RSA_SIZE 512 + +/* + * Maximum size for a RSA factor (in bits). This is for RSA private-key + * operations. Default is to support factors up to a bit more than half + * the maximum modulus size. + * + * This value MUST be a multiple of 32. + */ +#define BR_MAX_RSA_FACTOR ((BR_MAX_RSA_SIZE + 64) >> 1) + +/* + * Maximum size for an EC curve (modulus or order), in bits. Size of + * stack buffers depends on that parameter. This size MUST be a multiple + * of 8 (so that decoding an integer with that many bytes does not + * overflow). + */ +#define BR_MAX_EC_SIZE 528 + +/* + * Some macros to recognize the current architecture. Right now, we are + * interested into automatically recognizing architecture with efficient + * 64-bit types so that we may automatically use implementations that + * use 64-bit registers in that case. Future versions may detect, e.g., + * availability of SSE2 intrinsics. + * + * If 'unsigned long' is a 64-bit type, then we assume that 64-bit types + * are efficient. Otherwise, we rely on macros that depend on compiler, + * OS and architecture. In any case, failure to detect the architecture + * as 64-bit means that the 32-bit code will be used, and that code + * works also on 64-bit architectures (the 64-bit code may simply be + * more efficient). + * + * The test on 'unsigned long' should already catch most cases, the one + * notable exception being Windows code where 'unsigned long' is kept to + * 32-bit for compatibility with all the legacy code that liberally uses + * the 'DWORD' type for 32-bit values. + * + * Macro names are taken from: http://nadeausoftware.com/articles/2012/02/c_c_tip_how_detect_processor_type_using_compiler_predefined_macros + */ +#ifndef BR_64 +#if ((ULONG_MAX >> 31) >> 31) == 3 +#define BR_64 1 +#elif defined(__ia64) || defined(__itanium__) || defined(_M_IA64) +#define BR_64 1 +#elif defined(__powerpc64__) || defined(__ppc64__) || defined(__PPC64__) \ + || defined(__64BIT__) || defined(_LP64) || defined(__LP64__) +#define BR_64 1 +#elif defined(__sparc64__) +#define BR_64 1 +#elif defined(__x86_64__) || defined(_M_X64) +#define BR_64 1 +#elif defined(__aarch64__) || defined(_M_ARM64) +#define BR_64 1 +#elif defined(__mips64) +#define BR_64 1 +#endif +#endif + +/* + * Set BR_LOMUL on platforms where it makes sense. + */ +#ifndef BR_LOMUL +#if BR_ARMEL_CORTEXM_GCC +#define BR_LOMUL 1 +#endif +#endif + +/* + * Architecture detection. + */ +#ifndef BR_i386 +#if __i386__ || _M_IX86 +#define BR_i386 1 +#endif +#endif + +#ifndef BR_amd64 +#if __x86_64__ || _M_X64 +#define BR_amd64 1 +#endif +#endif + +/* + * Compiler brand and version. + * + * Implementations that use intrinsics need to detect the compiler type + * and version because some specific actions may be needed to activate + * the corresponding opcodes, both for header inclusion, and when using + * them in a function. + * + * BR_GCC, BR_CLANG and BR_MSC will be set to 1 for, respectively, GCC, + * Clang and MS Visual C. For each of them, sub-macros will be defined + * for versions; each sub-macro is set whenever the compiler version is + * at least as recent as the one corresponding to the macro. + */ + +/* + * GCC thresholds are on versions 4.4 to 4.9 and 5.0. + */ +#ifndef BR_GCC +#if __GNUC__ && !__clang__ +#define BR_GCC 1 + +#if __GNUC__ > 4 +#define BR_GCC_5_0 1 +#elif __GNUC__ == 4 && __GNUC_MINOR__ >= 9 +#define BR_GCC_4_9 1 +#elif __GNUC__ == 4 && __GNUC_MINOR__ >= 8 +#define BR_GCC_4_8 1 +#elif __GNUC__ == 4 && __GNUC_MINOR__ >= 7 +#define BR_GCC_4_7 1 +#elif __GNUC__ == 4 && __GNUC_MINOR__ >= 6 +#define BR_GCC_4_6 1 +#elif __GNUC__ == 4 && __GNUC_MINOR__ >= 5 +#define BR_GCC_4_5 1 +#elif __GNUC__ == 4 && __GNUC_MINOR__ >= 4 +#define BR_GCC_4_4 1 +#endif + +#if BR_GCC_5_0 +#define BR_GCC_4_9 1 +#endif +#if BR_GCC_4_9 +#define BR_GCC_4_8 1 +#endif +#if BR_GCC_4_8 +#define BR_GCC_4_7 1 +#endif +#if BR_GCC_4_7 +#define BR_GCC_4_6 1 +#endif +#if BR_GCC_4_6 +#define BR_GCC_4_5 1 +#endif +#if BR_GCC_4_5 +#define BR_GCC_4_4 1 +#endif + +#endif +#endif + +/* + * Clang thresholds are on versions 3.7.0 and 3.8.0. + */ +#ifndef BR_CLANG +#if __clang__ +#define BR_CLANG 1 + +#if __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 8) +#define BR_CLANG_3_8 1 +#elif __clang_major__ == 3 && __clang_minor__ >= 7 +#define BR_CLANG_3_7 1 +#endif + +#if BR_CLANG_3_8 +#define BR_CLANG_3_7 1 +#endif + +#endif +#endif + +/* + * MS Visual C thresholds are on Visual Studio 2005 to 2015. + */ +#ifndef BR_MSC +#if _MSC_VER +#define BR_MSC 1 + +#if _MSC_VER >= 1900 +#define BR_MSC_2015 1 +#elif _MSC_VER >= 1800 +#define BR_MSC_2013 1 +#elif _MSC_VER >= 1700 +#define BR_MSC_2012 1 +#elif _MSC_VER >= 1600 +#define BR_MSC_2010 1 +#elif _MSC_VER >= 1500 +#define BR_MSC_2008 1 +#elif _MSC_VER >= 1400 +#define BR_MSC_2005 1 +#endif + +#if BR_MSC_2015 +#define BR_MSC_2013 1 +#endif +#if BR_MSC_2013 +#define BR_MSC_2012 1 +#endif +#if BR_MSC_2012 +#define BR_MSC_2010 1 +#endif +#if BR_MSC_2010 +#define BR_MSC_2008 1 +#endif +#if BR_MSC_2008 +#define BR_MSC_2005 1 +#endif + +#endif +#endif + +/* + * GCC 4.4+ and Clang 3.7+ allow tagging specific functions with a + * 'target' attribute that activates support for specific opcodes. + */ +#if BR_GCC_4_4 || BR_CLANG_3_7 +#define BR_TARGET(x) __attribute__((target(x))) +#else +#define BR_TARGET(x) +#endif + +/* + * AES-NI intrinsics are available on x86 (32-bit and 64-bit) with + * GCC 4.8+, Clang 3.7+ and MSC 2012+. + */ +#ifndef BR_AES_X86NI +#if (BR_i386 || BR_amd64) && (BR_GCC_4_8 || BR_CLANG_3_7 || BR_MSC_2012) +#define BR_AES_X86NI 1 +#endif +#endif + +/* + * SSE2 intrinsics are available on x86 (32-bit and 64-bit) with + * GCC 4.4+, Clang 3.7+ and MSC 2005+. + */ +#ifndef BR_SSE2 +#if (BR_i386 || BR_amd64) && (BR_GCC_4_4 || BR_CLANG_3_7 || BR_MSC_2005) +#define BR_SSE2 1 +#endif +#endif + +/* + * RDRAND intrinsics are available on x86 (32-bit and 64-bit) with + * GCC 4.6+, Clang 3.7+ and MSC 2012+. + */ +#ifndef BR_RDRAND +#if (BR_i386 || BR_amd64) && (BR_GCC_4_6 || BR_CLANG_3_7 || BR_MSC_2012) +#define BR_RDRAND 1 +#endif +#endif + +/* + * Determine type of OS for random number generation. Macro names and + * values are documented on: + * https://sourceforge.net/p/predef/wiki/OperatingSystems/ + * + * Win32's CryptGenRandom() should be available on Windows systems. + * + * /dev/urandom should work on all Unix-like systems (including macOS X). + * + * getentropy() is present on Linux (Glibc 2.25+), FreeBSD (12.0+) and + * OpenBSD (5.6+). For OpenBSD, there does not seem to be easy to use + * macros to test the minimum version, so we just assume that it is + * recent enough (last version without getentropy() has gone out of + * support in May 2015). + * + * Ideally we should use getentropy() on macOS (10.12+) too, but I don't + * know how to test the exact OS version with preprocessor macros. + * + * TODO: enrich the list of detected system. + */ + +#ifndef BR_USE_URANDOM +#if defined _AIX \ + || defined __ANDROID__ \ + || defined __FreeBSD__ \ + || defined __NetBSD__ \ + || defined __OpenBSD__ \ + || defined __DragonFly__ \ + || defined __linux__ \ + || (defined __sun && (defined __SVR4 || defined __svr4__)) \ + || (defined __APPLE__ && defined __MACH__) +#define BR_USE_URANDOM 1 +#endif +#endif + +#ifndef BR_USE_GETENTROPY +#if (defined __linux__ \ + && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))) \ + || (defined __FreeBSD__ && __FreeBSD__ >= 12) \ + || defined __OpenBSD__ +#define BR_USE_GETENTROPY 1 +#endif +#endif + +#ifndef BR_USE_WIN32_RAND +#if defined _WIN32 || defined _WIN64 +#define BR_USE_WIN32_RAND 1 +#endif +#endif + +/* + * POWER8 crypto support. We rely on compiler macros for the + * architecture, since we do not have a reliable, simple way to detect + * the required support at runtime (we could try running an opcode, and + * trapping the exception or signal on illegal instruction, but this + * induces some non-trivial OS dependencies that we would prefer to + * avoid if possible). + */ +#ifndef BR_POWER8 +#if __GNUC__ && ((_ARCH_PWR8 || _ARCH_PPC) && __CRYPTO__) +#define BR_POWER8 1 +#endif +#endif + +/* + * Detect endinanness on POWER8. + */ +#if BR_POWER8 +#if defined BR_POWER8_LE +#undef BR_POWER8_BE +#if BR_POWER8_LE +#define BR_POWER8_BE 0 +#else +#define BR_POWER8_BE 1 +#endif +#elif defined BR_POWER8_BE +#undef BR_POWER8_LE +#if BR_POWER8_BE +#define BR_POWER8_LE 0 +#else +#define BR_POWER8_LE 1 +#endif +#else +#if __LITTLE_ENDIAN__ +#define BR_POWER8_LE 1 +#define BR_POWER8_BE 0 +#else +#define BR_POWER8_LE 0 +#define BR_POWER8_BE 1 +#endif +#endif +#endif + +/* + * Detect support for 128-bit integers. + */ +#if !defined BR_INT128 && !defined BR_UMUL128 +#ifdef __SIZEOF_INT128__ +#define BR_INT128 1 +#elif _M_X64 +#define BR_UMUL128 1 +#endif +#endif + +/* + * Detect support for unaligned accesses with known endianness. + * + * x86 (both 32-bit and 64-bit) is little-endian and allows unaligned + * accesses. + * + * POWER/PowerPC allows unaligned accesses when big-endian. POWER8 and + * later also allow unaligned accesses when little-endian. + */ +#if !defined BR_LE_UNALIGNED && !defined BR_BE_UNALIGNED + +#if __i386 || __i386__ || __x86_64__ || _M_IX86 || _M_X64 +#define BR_LE_UNALIGNED 1 +#elif BR_POWER8_BE +#define BR_BE_UNALIGNED 1 +#elif BR_POWER8_LE +#define BR_LE_UNALIGNED 1 +#elif (__powerpc__ || __powerpc64__ || _M_PPC || _ARCH_PPC || _ARCH_PPC64) \ + && __BIG_ENDIAN__ +#define BR_BE_UNALIGNED 1 +#endif + +#endif + +/* + * Detect support for an OS-provided time source. + */ + +#ifndef BR_USE_UNIX_TIME +#if defined __unix__ || defined __linux__ \ + || defined _POSIX_SOURCE || defined _POSIX_C_SOURCE \ + || (defined __APPLE__ && defined __MACH__) +#define BR_USE_UNIX_TIME 1 +#endif +#endif + +#ifndef BR_USE_WIN32_TIME +#if defined _WIN32 || defined _WIN64 +#define BR_USE_WIN32_TIME 1 +#endif +#endif + +/* ==================================================================== */ +/* + * Encoding/decoding functions. + * + * 32-bit and 64-bit decoding, both little-endian and big-endian, is + * implemented with the inline functions below. + * + * When allowed by some compile-time options (autodetected or provided), + * optimised code is used, to perform direct memory access when the + * underlying architecture supports it, both for endianness and + * alignment. This, however, may trigger strict aliasing issues; the + * code below uses unions to perform (supposedly) safe type punning. + * Since the C aliasing rules are relatively complex and were amended, + * or at least re-explained with different phrasing, in all successive + * versions of the C standard, it is always a bit risky to bet that any + * specific version of a C compiler got it right, for some notion of + * "right". + */ + +typedef union { + uint16_t u; + unsigned char b[sizeof(uint16_t)]; +} br_union_u16; + +typedef union { + uint32_t u; + unsigned char b[sizeof(uint32_t)]; +} br_union_u32; + +typedef union { + uint64_t u; + unsigned char b[sizeof(uint64_t)]; +} br_union_u64; + +static inline void +br_enc16le(void *dst, unsigned x) +{ +#if BR_LE_UNALIGNED + ((br_union_u16 *)dst)->u = x; +#else + unsigned char *buf; + + buf = dst; + buf[0] = (unsigned char)x; + buf[1] = (unsigned char)(x >> 8); +#endif +} + +static inline void +br_enc16be(void *dst, unsigned x) +{ +#if BR_BE_UNALIGNED + ((br_union_u16 *)dst)->u = x; +#else + unsigned char *buf; + + buf = dst; + buf[0] = (unsigned char)(x >> 8); + buf[1] = (unsigned char)x; +#endif +} + +static inline unsigned +br_dec16le(const void *src) +{ +#if BR_LE_UNALIGNED + return ((const br_union_u16 *)src)->u; +#else + const unsigned char *buf; + + buf = src; + return (unsigned)buf[0] | ((unsigned)buf[1] << 8); +#endif +} + +static inline unsigned +br_dec16be(const void *src) +{ +#if BR_BE_UNALIGNED + return ((const br_union_u16 *)src)->u; +#else + const unsigned char *buf; + + buf = src; + return ((unsigned)buf[0] << 8) | (unsigned)buf[1]; +#endif +} + +static inline void +br_enc32le(void *dst, uint32_t x) +{ +#if BR_LE_UNALIGNED + ((br_union_u32 *)dst)->u = x; +#else + unsigned char *buf; + + buf = dst; + buf[0] = (unsigned char)x; + buf[1] = (unsigned char)(x >> 8); + buf[2] = (unsigned char)(x >> 16); + buf[3] = (unsigned char)(x >> 24); +#endif +} + +static inline void +br_enc32be(void *dst, uint32_t x) +{ +#if BR_BE_UNALIGNED + ((br_union_u32 *)dst)->u = x; +#else + unsigned char *buf; + + buf = dst; + buf[0] = (unsigned char)(x >> 24); + buf[1] = (unsigned char)(x >> 16); + buf[2] = (unsigned char)(x >> 8); + buf[3] = (unsigned char)x; +#endif +} + +static inline uint32_t +br_dec32le(const void *src) +{ +#if BR_LE_UNALIGNED + return ((const br_union_u32 *)src)->u; +#else + const unsigned char *buf; + + buf = src; + return (uint32_t)buf[0] + | ((uint32_t)buf[1] << 8) + | ((uint32_t)buf[2] << 16) + | ((uint32_t)buf[3] << 24); +#endif +} + +static inline uint32_t +br_dec32be(const void *src) +{ +#if BR_BE_UNALIGNED + return ((const br_union_u32 *)src)->u; +#else + const unsigned char *buf; + + buf = src; + return ((uint32_t)buf[0] << 24) + | ((uint32_t)buf[1] << 16) + | ((uint32_t)buf[2] << 8) + | (uint32_t)buf[3]; +#endif +} + +static inline void +br_enc64le(void *dst, uint64_t x) +{ +#if BR_LE_UNALIGNED + ((br_union_u64 *)dst)->u = x; +#else + unsigned char *buf; + + buf = dst; + br_enc32le(buf, (uint32_t)x); + br_enc32le(buf + 4, (uint32_t)(x >> 32)); +#endif +} + +static inline void +br_enc64be(void *dst, uint64_t x) +{ +#if BR_BE_UNALIGNED + ((br_union_u64 *)dst)->u = x; +#else + unsigned char *buf; + + buf = dst; + br_enc32be(buf, (uint32_t)(x >> 32)); + br_enc32be(buf + 4, (uint32_t)x); +#endif +} + +static inline uint64_t +br_dec64le(const void *src) +{ +#if BR_LE_UNALIGNED + return ((const br_union_u64 *)src)->u; +#else + const unsigned char *buf; + + buf = src; + return (uint64_t)br_dec32le(buf) + | ((uint64_t)br_dec32le(buf + 4) << 32); +#endif +} + +static inline uint64_t +br_dec64be(const void *src) +{ +#if BR_BE_UNALIGNED + return ((const br_union_u64 *)src)->u; +#else + const unsigned char *buf; + + buf = src; + return ((uint64_t)br_dec32be(buf) << 32) + | (uint64_t)br_dec32be(buf + 4); +#endif +} + +/* + * Range decoding and encoding (for several successive values). + */ +void br_range_dec16le(uint16_t *v, size_t num, const void *src); +void br_range_dec16be(uint16_t *v, size_t num, const void *src); +void br_range_enc16le(void *dst, const uint16_t *v, size_t num); +void br_range_enc16be(void *dst, const uint16_t *v, size_t num); + +void br_range_dec32le(uint32_t *v, size_t num, const void *src); +void br_range_dec32be(uint32_t *v, size_t num, const void *src); +void br_range_enc32le(void *dst, const uint32_t *v, size_t num); +void br_range_enc32be(void *dst, const uint32_t *v, size_t num); + +void br_range_dec64le(uint64_t *v, size_t num, const void *src); +void br_range_dec64be(uint64_t *v, size_t num, const void *src); +void br_range_enc64le(void *dst, const uint64_t *v, size_t num); +void br_range_enc64be(void *dst, const uint64_t *v, size_t num); + +/* + * Byte-swap a 32-bit integer. + */ +static inline uint32_t +br_swap32(uint32_t x) +{ + x = ((x & (uint32_t)0x00FF00FF) << 8) + | ((x >> 8) & (uint32_t)0x00FF00FF); + return (x << 16) | (x >> 16); +} + +/* ==================================================================== */ +/* + * Support code for hash functions. + */ + +/* + * IV for MD5, SHA-1, SHA-224 and SHA-256. + */ +extern const uint32_t br_md5_IV[]; +extern const uint32_t br_sha1_IV[]; +extern const uint32_t br_sha224_IV[]; +extern const uint32_t br_sha256_IV[]; + +/* + * Round functions for MD5, SHA-1, SHA-224 and SHA-256 (SHA-224 and + * SHA-256 use the same round function). + */ +void br_md5_round(const unsigned char *buf, uint32_t *val); +void br_sha1_round(const unsigned char *buf, uint32_t *val); +void br_sha2small_round(const unsigned char *buf, uint32_t *val); + +/* + * The core function for the TLS PRF. It computes + * P_hash(secret, label + seed), and XORs the result into the dst buffer. + */ +void br_tls_phash(void *dst, size_t len, + const br_hash_class *dig, + const void *secret, size_t secret_len, const char *label, + size_t seed_num, const br_tls_prf_seed_chunk *seed); + +/* + * Copy all configured hash implementations from a multihash context + * to another. + */ +static inline void +br_multihash_copyimpl(br_multihash_context *dst, + const br_multihash_context *src) +{ + memcpy((void *)dst->impl, src->impl, sizeof src->impl); +} + +/* ==================================================================== */ +/* + * Constant-time primitives. These functions manipulate 32-bit values in + * order to provide constant-time comparisons and multiplexers. + * + * Boolean values (the "ctl" bits) MUST have value 0 or 1. + * + * Implementation notes: + * ===================== + * + * The uintN_t types are unsigned and with width exactly N bits; the C + * standard guarantees that computations are performed modulo 2^N, and + * there can be no overflow. Negation (unary '-') works on unsigned types + * as well. + * + * The intN_t types are guaranteed to have width exactly N bits, with no + * padding bit, and using two's complement representation. Casting + * intN_t to uintN_t really is conversion modulo 2^N. Beware that intN_t + * types, being signed, trigger implementation-defined behaviour on + * overflow (including raising some signal): with GCC, while modular + * arithmetics are usually applied, the optimizer may assume that + * overflows don't occur (unless the -fwrapv command-line option is + * added); Clang has the additional -ftrapv option to explicitly trap on + * integer overflow or underflow. + */ + +/* + * Negate a boolean. + */ +static inline uint32_t +NOT(uint32_t ctl) +{ + return ctl ^ 1; +} + +/* + * Multiplexer: returns x if ctl == 1, y if ctl == 0. + */ +static inline uint32_t +MUX(uint32_t ctl, uint32_t x, uint32_t y) +{ + return y ^ (-ctl & (x ^ y)); +} + +/* + * Equality check: returns 1 if x == y, 0 otherwise. + */ +static inline uint32_t +EQ(uint32_t x, uint32_t y) +{ + uint32_t q; + + q = x ^ y; + return NOT((q | -q) >> 31); +} + +/* + * Inequality check: returns 1 if x != y, 0 otherwise. + */ +static inline uint32_t +NEQ(uint32_t x, uint32_t y) +{ + uint32_t q; + + q = x ^ y; + return (q | -q) >> 31; +} + +/* + * Comparison: returns 1 if x > y, 0 otherwise. + */ +static inline uint32_t +GT(uint32_t x, uint32_t y) +{ + /* + * If both x < 2^31 and x < 2^31, then y-x will have its high + * bit set if x > y, cleared otherwise. + * + * If either x >= 2^31 or y >= 2^31 (but not both), then the + * result is the high bit of x. + * + * If both x >= 2^31 and y >= 2^31, then we can virtually + * subtract 2^31 from both, and we are back to the first case. + * Since (y-2^31)-(x-2^31) = y-x, the subtraction is already + * fine. + */ + uint32_t z; + + z = y - x; + return (z ^ ((x ^ y) & (x ^ z))) >> 31; +} + +/* + * Other comparisons (greater-or-equal, lower-than, lower-or-equal). + */ +#define GE(x, y) NOT(GT(y, x)) +#define LT(x, y) GT(y, x) +#define LE(x, y) NOT(GT(x, y)) + +/* + * General comparison: returned value is -1, 0 or 1, depending on + * whether x is lower than, equal to, or greater than y. + */ +static inline int32_t +CMP(uint32_t x, uint32_t y) +{ + return (int32_t)GT(x, y) | -(int32_t)GT(y, x); +} + +/* + * Returns 1 if x == 0, 0 otherwise. Take care that the operand is signed. + */ +static inline uint32_t +EQ0(int32_t x) +{ + uint32_t q; + + q = (uint32_t)x; + return ~(q | -q) >> 31; +} + +/* + * Returns 1 if x > 0, 0 otherwise. Take care that the operand is signed. + */ +static inline uint32_t +GT0(int32_t x) +{ + /* + * High bit of -x is 0 if x == 0, but 1 if x > 0. + */ + uint32_t q; + + q = (uint32_t)x; + return (~q & -q) >> 31; +} + +/* + * Returns 1 if x >= 0, 0 otherwise. Take care that the operand is signed. + */ +static inline uint32_t +GE0(int32_t x) +{ + return ~(uint32_t)x >> 31; +} + +/* + * Returns 1 if x < 0, 0 otherwise. Take care that the operand is signed. + */ +static inline uint32_t +LT0(int32_t x) +{ + return (uint32_t)x >> 31; +} + +/* + * Returns 1 if x <= 0, 0 otherwise. Take care that the operand is signed. + */ +static inline uint32_t +LE0(int32_t x) +{ + uint32_t q; + + /* + * ~-x has its high bit set if and only if -x is nonnegative (as + * a signed int), i.e. x is in the -(2^31-1) to 0 range. We must + * do an OR with x itself to account for x = -2^31. + */ + q = (uint32_t)x; + return (q | ~-q) >> 31; +} + +/* + * Conditional copy: src[] is copied into dst[] if and only if ctl is 1. + * dst[] and src[] may overlap completely (but not partially). + */ +void br_ccopy(uint32_t ctl, void *dst, const void *src, size_t len); + +#define CCOPY br_ccopy + +/* + * Compute the bit length of a 32-bit integer. Returned value is between 0 + * and 32 (inclusive). + */ +static inline uint32_t +BIT_LENGTH(uint32_t x) +{ + uint32_t k, c; + + k = NEQ(x, 0); + c = GT(x, 0xFFFF); x = MUX(c, x >> 16, x); k += c << 4; + c = GT(x, 0x00FF); x = MUX(c, x >> 8, x); k += c << 3; + c = GT(x, 0x000F); x = MUX(c, x >> 4, x); k += c << 2; + c = GT(x, 0x0003); x = MUX(c, x >> 2, x); k += c << 1; + k += GT(x, 0x0001); + return k; +} + +/* + * Compute the minimum of x and y. + */ +static inline uint32_t +MIN(uint32_t x, uint32_t y) +{ + return MUX(GT(x, y), y, x); +} + +/* + * Compute the maximum of x and y. + */ +static inline uint32_t +MAX(uint32_t x, uint32_t y) +{ + return MUX(GT(x, y), x, y); +} + +/* + * Multiply two 32-bit integers, with a 64-bit result. This default + * implementation assumes that the basic multiplication operator + * yields constant-time code. + */ +#define MUL(x, y) ((uint64_t)(x) * (uint64_t)(y)) + +#if BR_CT_MUL31 + +/* + * Alternate implementation of MUL31, that will be constant-time on some + * (old) platforms where the default MUL31 is not. Unfortunately, it is + * also substantially slower, and yields larger code, on more modern + * platforms, which is why it is deactivated by default. + * + * MUL31_lo() must do some extra work because on some platforms, the + * _signed_ multiplication may return early if the top bits are 1. + * Simply truncating (casting) the output of MUL31() would not be + * sufficient, because the compiler may notice that we keep only the low + * word, and then replace automatically the unsigned multiplication with + * a signed multiplication opcode. + */ +#define MUL31(x, y) ((uint64_t)((x) | (uint32_t)0x80000000) \ + * (uint64_t)((y) | (uint32_t)0x80000000) \ + - ((uint64_t)(x) << 31) - ((uint64_t)(y) << 31) \ + - ((uint64_t)1 << 62)) +static inline uint32_t +MUL31_lo(uint32_t x, uint32_t y) +{ + uint32_t xl, xh; + uint32_t yl, yh; + + xl = (x & 0xFFFF) | (uint32_t)0x80000000; + xh = (x >> 16) | (uint32_t)0x80000000; + yl = (y & 0xFFFF) | (uint32_t)0x80000000; + yh = (y >> 16) | (uint32_t)0x80000000; + return (xl * yl + ((xl * yh + xh * yl) << 16)) & (uint32_t)0x7FFFFFFF; +} + +#else + +/* + * Multiply two 31-bit integers, with a 62-bit result. This default + * implementation assumes that the basic multiplication operator + * yields constant-time code. + * The MUL31_lo() macro returns only the low 31 bits of the product. + */ +#define MUL31(x, y) ((uint64_t)(x) * (uint64_t)(y)) +#define MUL31_lo(x, y) (((uint32_t)(x) * (uint32_t)(y)) & (uint32_t)0x7FFFFFFF) + +#endif + +/* + * Multiply two words together; the sum of the lengths of the two + * operands must not exceed 31 (for instance, one operand may use 16 + * bits if the other fits on 15). If BR_CT_MUL15 is non-zero, then the + * macro will contain some extra operations that help in making the + * operation constant-time on some platforms, where the basic 32-bit + * multiplication is not constant-time. + */ +#if BR_CT_MUL15 +#define MUL15(x, y) (((uint32_t)(x) | (uint32_t)0x80000000) \ + * ((uint32_t)(y) | (uint32_t)0x80000000) \ + & (uint32_t)0x7FFFFFFF) +#else +#define MUL15(x, y) ((uint32_t)(x) * (uint32_t)(y)) +#endif + +/* + * Arithmetic right shift (sign bit is copied). What happens when + * right-shifting a negative value is _implementation-defined_, so it + * does not trigger undefined behaviour, but it is still up to each + * compiler to define (and document) what it does. Most/all compilers + * will do an arithmetic shift, the sign bit being used to fill the + * holes; this is a native operation on the underlying CPU, and it would + * make little sense for the compiler to do otherwise. GCC explicitly + * documents that it follows that convention. + * + * Still, if BR_NO_ARITH_SHIFT is defined (and non-zero), then an + * alternate version will be used, that does not rely on such + * implementation-defined behaviour. Unfortunately, it is also slower + * and yields bigger code, which is why it is deactivated by default. + */ +#if BR_NO_ARITH_SHIFT +#define ARSH(x, n) (((uint32_t)(x) >> (n)) \ + | ((-((uint32_t)(x) >> 31)) << (32 - (n)))) +#else +#define ARSH(x, n) ((*(int32_t *)&(x)) >> (n)) +#endif + +/* + * Constant-time division. The dividend hi:lo is divided by the + * divisor d; the quotient is returned and the remainder is written + * in *r. If hi == d, then the quotient does not fit on 32 bits; + * returned value is thus truncated. If hi > d, returned values are + * indeterminate. + */ +uint32_t br_divrem(uint32_t hi, uint32_t lo, uint32_t d, uint32_t *r); + +/* + * Wrapper for br_divrem(); the remainder is returned, and the quotient + * is discarded. + */ +static inline uint32_t +br_rem(uint32_t hi, uint32_t lo, uint32_t d) +{ + uint32_t r; + + br_divrem(hi, lo, d, &r); + return r; +} + +/* + * Wrapper for br_divrem(); the quotient is returned, and the remainder + * is discarded. + */ +static inline uint32_t +br_div(uint32_t hi, uint32_t lo, uint32_t d) +{ + uint32_t r; + + return br_divrem(hi, lo, d, &r); +} + +/* ==================================================================== */ + +/* + * Integers 'i32' + * -------------- + * + * The 'i32' functions implement computations on big integers using + * an internal representation as an array of 32-bit integers. For + * an array x[]: + * -- x[0] contains the "announced bit length" of the integer + * -- x[1], x[2]... contain the value in little-endian order (x[1] + * contains the least significant 32 bits) + * + * Multiplications rely on the elementary 32x32->64 multiplication. + * + * The announced bit length specifies the number of bits that are + * significant in the subsequent 32-bit words. Unused bits in the + * last (most significant) word are set to 0; subsequent words are + * uninitialized and need not exist at all. + * + * The execution time and memory access patterns of all computations + * depend on the announced bit length, but not on the actual word + * values. For modular integers, the announced bit length of any integer + * modulo n is equal to the actual bit length of n; thus, computations + * on modular integers are "constant-time" (only the modulus length may + * leak). + */ + +/* + * Compute the actual bit length of an integer. The argument x should + * point to the first (least significant) value word of the integer. + * The len 'xlen' contains the number of 32-bit words to access. + * + * CT: value or length of x does not leak. + */ +uint32_t br_i32_bit_length(uint32_t *x, size_t xlen); + +/* + * Decode an integer from its big-endian unsigned representation. The + * "true" bit length of the integer is computed, but all words of x[] + * corresponding to the full 'len' bytes of the source are set. + * + * CT: value or length of x does not leak. + */ +void br_i32_decode(uint32_t *x, const void *src, size_t len); + +/* + * Decode an integer from its big-endian unsigned representation. The + * integer MUST be lower than m[]; the announced bit length written in + * x[] will be equal to that of m[]. All 'len' bytes from the source are + * read. + * + * Returned value is 1 if the decode value fits within the modulus, 0 + * otherwise. In the latter case, the x[] buffer will be set to 0 (but + * still with the announced bit length of m[]). + * + * CT: value or length of x does not leak. Memory access pattern depends + * only of 'len' and the announced bit length of m. Whether x fits or + * not does not leak either. + */ +uint32_t br_i32_decode_mod(uint32_t *x, + const void *src, size_t len, const uint32_t *m); + +/* + * Reduce an integer (a[]) modulo another (m[]). The result is written + * in x[] and its announced bit length is set to be equal to that of m[]. + * + * x[] MUST be distinct from a[] and m[]. + * + * CT: only announced bit lengths leak, not values of x, a or m. + */ +void br_i32_reduce(uint32_t *x, const uint32_t *a, const uint32_t *m); + +/* + * Decode an integer from its big-endian unsigned representation, and + * reduce it modulo the provided modulus m[]. The announced bit length + * of the result is set to be equal to that of the modulus. + * + * x[] MUST be distinct from m[]. + */ +void br_i32_decode_reduce(uint32_t *x, + const void *src, size_t len, const uint32_t *m); + +/* + * Encode an integer into its big-endian unsigned representation. The + * output length in bytes is provided (parameter 'len'); if the length + * is too short then the integer is appropriately truncated; if it is + * too long then the extra bytes are set to 0. + */ +void br_i32_encode(void *dst, size_t len, const uint32_t *x); + +/* + * Multiply x[] by 2^32 and then add integer z, modulo m[]. This + * function assumes that x[] and m[] have the same announced bit + * length, and the announced bit length of m[] matches its true + * bit length. + * + * x[] and m[] MUST be distinct arrays. + * + * CT: only the common announced bit length of x and m leaks, not + * the values of x, z or m. + */ +void br_i32_muladd_small(uint32_t *x, uint32_t z, const uint32_t *m); + +/* + * Extract one word from an integer. The offset is counted in bits. + * The word MUST entirely fit within the word elements corresponding + * to the announced bit length of a[]. + */ +static inline uint32_t +br_i32_word(const uint32_t *a, uint32_t off) +{ + size_t u; + unsigned j; + + u = (size_t)(off >> 5) + 1; + j = (unsigned)off & 31; + if (j == 0) { + return a[u]; + } else { + return (a[u] >> j) | (a[u + 1] << (32 - j)); + } +} + +/* + * Test whether an integer is zero. + */ +uint32_t br_i32_iszero(const uint32_t *x); + +/* + * Add b[] to a[] and return the carry (0 or 1). If ctl is 0, then a[] + * is unmodified, but the carry is still computed and returned. The + * arrays a[] and b[] MUST have the same announced bit length. + * + * a[] and b[] MAY be the same array, but partial overlap is not allowed. + */ +uint32_t br_i32_add(uint32_t *a, const uint32_t *b, uint32_t ctl); + +/* + * Subtract b[] from a[] and return the carry (0 or 1). If ctl is 0, + * then a[] is unmodified, but the carry is still computed and returned. + * The arrays a[] and b[] MUST have the same announced bit length. + * + * a[] and b[] MAY be the same array, but partial overlap is not allowed. + */ +uint32_t br_i32_sub(uint32_t *a, const uint32_t *b, uint32_t ctl); + +/* + * Compute d+a*b, result in d. The initial announced bit length of d[] + * MUST match that of a[]. The d[] array MUST be large enough to + * accommodate the full result, plus (possibly) an extra word. The + * resulting announced bit length of d[] will be the sum of the announced + * bit lengths of a[] and b[] (therefore, it may be larger than the actual + * bit length of the numerical result). + * + * a[] and b[] may be the same array. d[] must be disjoint from both a[] + * and b[]. + */ +void br_i32_mulacc(uint32_t *d, const uint32_t *a, const uint32_t *b); + +/* + * Zeroize an integer. The announced bit length is set to the provided + * value, and the corresponding words are set to 0. + */ +static inline void +br_i32_zero(uint32_t *x, uint32_t bit_len) +{ + *x ++ = bit_len; + memset(x, 0, ((bit_len + 31) >> 5) * sizeof *x); +} + +/* + * Compute -(1/x) mod 2^32. If x is even, then this function returns 0. + */ +uint32_t br_i32_ninv32(uint32_t x); + +/* + * Convert a modular integer to Montgomery representation. The integer x[] + * MUST be lower than m[], but with the same announced bit length. + */ +void br_i32_to_monty(uint32_t *x, const uint32_t *m); + +/* + * Convert a modular integer back from Montgomery representation. The + * integer x[] MUST be lower than m[], but with the same announced bit + * length. The "m0i" parameter is equal to -(1/m0) mod 2^32, where m0 is + * the least significant value word of m[] (this works only if m[] is + * an odd integer). + */ +void br_i32_from_monty(uint32_t *x, const uint32_t *m, uint32_t m0i); + +/* + * Compute a modular Montgomery multiplication. d[] is filled with the + * value of x*y/R modulo m[] (where R is the Montgomery factor). The + * array d[] MUST be distinct from x[], y[] and m[]. x[] and y[] MUST be + * numerically lower than m[]. x[] and y[] MAY be the same array. The + * "m0i" parameter is equal to -(1/m0) mod 2^32, where m0 is the least + * significant value word of m[] (this works only if m[] is an odd + * integer). + */ +void br_i32_montymul(uint32_t *d, const uint32_t *x, const uint32_t *y, + const uint32_t *m, uint32_t m0i); + +/* + * Compute a modular exponentiation. x[] MUST be an integer modulo m[] + * (same announced bit length, lower value). m[] MUST be odd. The + * exponent is in big-endian unsigned notation, over 'elen' bytes. The + * "m0i" parameter is equal to -(1/m0) mod 2^32, where m0 is the least + * significant value word of m[] (this works only if m[] is an odd + * integer). The t1[] and t2[] parameters must be temporary arrays, + * each large enough to accommodate an integer with the same size as m[]. + */ +void br_i32_modpow(uint32_t *x, const unsigned char *e, size_t elen, + const uint32_t *m, uint32_t m0i, uint32_t *t1, uint32_t *t2); + +/* ==================================================================== */ + +/* + * Integers 'i31' + * -------------- + * + * The 'i31' functions implement computations on big integers using + * an internal representation as an array of 32-bit integers. For + * an array x[]: + * -- x[0] encodes the array length and the "announced bit length" + * of the integer: namely, if the announced bit length is k, + * then x[0] = ((k / 31) << 5) + (k % 31). + * -- x[1], x[2]... contain the value in little-endian order, 31 + * bits per word (x[1] contains the least significant 31 bits). + * The upper bit of each word is 0. + * + * Multiplications rely on the elementary 32x32->64 multiplication. + * + * The announced bit length specifies the number of bits that are + * significant in the subsequent 32-bit words. Unused bits in the + * last (most significant) word are set to 0; subsequent words are + * uninitialized and need not exist at all. + * + * The execution time and memory access patterns of all computations + * depend on the announced bit length, but not on the actual word + * values. For modular integers, the announced bit length of any integer + * modulo n is equal to the actual bit length of n; thus, computations + * on modular integers are "constant-time" (only the modulus length may + * leak). + */ + +/* + * Test whether an integer is zero. + */ +uint32_t br_i31_iszero(const uint32_t *x); + +/* + * Add b[] to a[] and return the carry (0 or 1). If ctl is 0, then a[] + * is unmodified, but the carry is still computed and returned. The + * arrays a[] and b[] MUST have the same announced bit length. + * + * a[] and b[] MAY be the same array, but partial overlap is not allowed. + */ +uint32_t br_i31_add(uint32_t *a, const uint32_t *b, uint32_t ctl); + +/* + * Subtract b[] from a[] and return the carry (0 or 1). If ctl is 0, + * then a[] is unmodified, but the carry is still computed and returned. + * The arrays a[] and b[] MUST have the same announced bit length. + * + * a[] and b[] MAY be the same array, but partial overlap is not allowed. + */ +uint32_t br_i31_sub(uint32_t *a, const uint32_t *b, uint32_t ctl); + +/* + * Compute the ENCODED actual bit length of an integer. The argument x + * should point to the first (least significant) value word of the + * integer. The len 'xlen' contains the number of 32-bit words to + * access. The upper bit of each value word MUST be 0. + * Returned value is ((k / 31) << 5) + (k % 31) if the bit length is k. + * + * CT: value or length of x does not leak. + */ +uint32_t br_i31_bit_length(uint32_t *x, size_t xlen); + +/* + * Decode an integer from its big-endian unsigned representation. The + * "true" bit length of the integer is computed and set in the encoded + * announced bit length (x[0]), but all words of x[] corresponding to + * the full 'len' bytes of the source are set. + * + * CT: value or length of x does not leak. + */ +void br_i31_decode(uint32_t *x, const void *src, size_t len); + +/* + * Decode an integer from its big-endian unsigned representation. The + * integer MUST be lower than m[]; the (encoded) announced bit length + * written in x[] will be equal to that of m[]. All 'len' bytes from the + * source are read. + * + * Returned value is 1 if the decode value fits within the modulus, 0 + * otherwise. In the latter case, the x[] buffer will be set to 0 (but + * still with the announced bit length of m[]). + * + * CT: value or length of x does not leak. Memory access pattern depends + * only of 'len' and the announced bit length of m. Whether x fits or + * not does not leak either. + */ +uint32_t br_i31_decode_mod(uint32_t *x, + const void *src, size_t len, const uint32_t *m); + +/* + * Zeroize an integer. The announced bit length is set to the provided + * value, and the corresponding words are set to 0. The ENCODED bit length + * is expected here. + */ +static inline void +br_i31_zero(uint32_t *x, uint32_t bit_len) +{ + *x ++ = bit_len; + memset(x, 0, ((bit_len + 31) >> 5) * sizeof *x); +} + +/* + * Right-shift an integer. The shift amount must be lower than 31 + * bits. + */ +void br_i31_rshift(uint32_t *x, int count); + +/* + * Reduce an integer (a[]) modulo another (m[]). The result is written + * in x[] and its announced bit length is set to be equal to that of m[]. + * + * x[] MUST be distinct from a[] and m[]. + * + * CT: only announced bit lengths leak, not values of x, a or m. + */ +void br_i31_reduce(uint32_t *x, const uint32_t *a, const uint32_t *m); + +/* + * Decode an integer from its big-endian unsigned representation, and + * reduce it modulo the provided modulus m[]. The announced bit length + * of the result is set to be equal to that of the modulus. + * + * x[] MUST be distinct from m[]. + */ +void br_i31_decode_reduce(uint32_t *x, + const void *src, size_t len, const uint32_t *m); + +/* + * Multiply x[] by 2^31 and then add integer z, modulo m[]. This + * function assumes that x[] and m[] have the same announced bit + * length, the announced bit length of m[] matches its true + * bit length. + * + * x[] and m[] MUST be distinct arrays. z MUST fit in 31 bits (upper + * bit set to 0). + * + * CT: only the common announced bit length of x and m leaks, not + * the values of x, z or m. + */ +void br_i31_muladd_small(uint32_t *x, uint32_t z, const uint32_t *m); + +/* + * Encode an integer into its big-endian unsigned representation. The + * output length in bytes is provided (parameter 'len'); if the length + * is too short then the integer is appropriately truncated; if it is + * too long then the extra bytes are set to 0. + */ +void br_i31_encode(void *dst, size_t len, const uint32_t *x); + +/* + * Compute -(1/x) mod 2^31. If x is even, then this function returns 0. + */ +uint32_t br_i31_ninv31(uint32_t x); + +/* + * Compute a modular Montgomery multiplication. d[] is filled with the + * value of x*y/R modulo m[] (where R is the Montgomery factor). The + * array d[] MUST be distinct from x[], y[] and m[]. x[] and y[] MUST be + * numerically lower than m[]. x[] and y[] MAY be the same array. The + * "m0i" parameter is equal to -(1/m0) mod 2^31, where m0 is the least + * significant value word of m[] (this works only if m[] is an odd + * integer). + */ +void br_i31_montymul(uint32_t *d, const uint32_t *x, const uint32_t *y, + const uint32_t *m, uint32_t m0i); + +/* + * Convert a modular integer to Montgomery representation. The integer x[] + * MUST be lower than m[], but with the same announced bit length. + */ +void br_i31_to_monty(uint32_t *x, const uint32_t *m); + +/* + * Convert a modular integer back from Montgomery representation. The + * integer x[] MUST be lower than m[], but with the same announced bit + * length. The "m0i" parameter is equal to -(1/m0) mod 2^32, where m0 is + * the least significant value word of m[] (this works only if m[] is + * an odd integer). + */ +void br_i31_from_monty(uint32_t *x, const uint32_t *m, uint32_t m0i); + +/* + * Compute a modular exponentiation. x[] MUST be an integer modulo m[] + * (same announced bit length, lower value). m[] MUST be odd. The + * exponent is in big-endian unsigned notation, over 'elen' bytes. The + * "m0i" parameter is equal to -(1/m0) mod 2^31, where m0 is the least + * significant value word of m[] (this works only if m[] is an odd + * integer). The t1[] and t2[] parameters must be temporary arrays, + * each large enough to accommodate an integer with the same size as m[]. + */ +void br_i31_modpow(uint32_t *x, const unsigned char *e, size_t elen, + const uint32_t *m, uint32_t m0i, uint32_t *t1, uint32_t *t2); + +/* + * Compute a modular exponentiation. x[] MUST be an integer modulo m[] + * (same announced bit length, lower value). m[] MUST be odd. The + * exponent is in big-endian unsigned notation, over 'elen' bytes. The + * "m0i" parameter is equal to -(1/m0) mod 2^31, where m0 is the least + * significant value word of m[] (this works only if m[] is an odd + * integer). The tmp[] array is used for temporaries, and has size + * 'twlen' words; it must be large enough to accommodate at least two + * temporary values with the same size as m[] (including the leading + * "bit length" word). If there is room for more temporaries, then this + * function may use the extra room for window-based optimisation, + * resulting in faster computations. + * + * Returned value is 1 on success, 0 on error. An error is reported if + * the provided tmp[] array is too short. + */ +uint32_t br_i31_modpow_opt(uint32_t *x, const unsigned char *e, size_t elen, + const uint32_t *m, uint32_t m0i, uint32_t *tmp, size_t twlen); + +/* + * Compute d+a*b, result in d. The initial announced bit length of d[] + * MUST match that of a[]. The d[] array MUST be large enough to + * accommodate the full result, plus (possibly) an extra word. The + * resulting announced bit length of d[] will be the sum of the announced + * bit lengths of a[] and b[] (therefore, it may be larger than the actual + * bit length of the numerical result). + * + * a[] and b[] may be the same array. d[] must be disjoint from both a[] + * and b[]. + */ +void br_i31_mulacc(uint32_t *d, const uint32_t *a, const uint32_t *b); + +/* + * Compute x/y mod m, result in x. Values x and y must be between 0 and + * m-1, and have the same announced bit length as m. Modulus m must be + * odd. The "m0i" parameter is equal to -1/m mod 2^31. The array 't' + * must point to a temporary area that can hold at least three integers + * of the size of m. + * + * m may not overlap x and y. x and y may overlap each other (this can + * be useful to test whether a value is invertible modulo m). t must be + * disjoint from all other arrays. + * + * Returned value is 1 on success, 0 otherwise. Success is attained if + * y is invertible modulo m. + */ +uint32_t br_i31_moddiv(uint32_t *x, const uint32_t *y, + const uint32_t *m, uint32_t m0i, uint32_t *t); + +/* ==================================================================== */ + +/* + * FIXME: document "i15" functions. + */ + +static inline void +br_i15_zero(uint16_t *x, uint16_t bit_len) +{ + *x ++ = bit_len; + memset(x, 0, ((bit_len + 15) >> 4) * sizeof *x); +} + +uint32_t br_i15_iszero(const uint16_t *x); + +uint16_t br_i15_ninv15(uint16_t x); + +uint32_t br_i15_add(uint16_t *a, const uint16_t *b, uint32_t ctl); + +uint32_t br_i15_sub(uint16_t *a, const uint16_t *b, uint32_t ctl); + +void br_i15_muladd_small(uint16_t *x, uint16_t z, const uint16_t *m); + +void br_i15_montymul(uint16_t *d, const uint16_t *x, const uint16_t *y, + const uint16_t *m, uint16_t m0i); + +void br_i15_to_monty(uint16_t *x, const uint16_t *m); + +void br_i15_modpow(uint16_t *x, const unsigned char *e, size_t elen, + const uint16_t *m, uint16_t m0i, uint16_t *t1, uint16_t *t2); + +uint32_t br_i15_modpow_opt(uint16_t *x, const unsigned char *e, size_t elen, + const uint16_t *m, uint16_t m0i, uint16_t *tmp, size_t twlen); + +void br_i15_encode(void *dst, size_t len, const uint16_t *x); + +uint32_t br_i15_decode_mod(uint16_t *x, + const void *src, size_t len, const uint16_t *m); + +void br_i15_rshift(uint16_t *x, int count); + +uint32_t br_i15_bit_length(uint16_t *x, size_t xlen); + +void br_i15_decode(uint16_t *x, const void *src, size_t len); + +void br_i15_from_monty(uint16_t *x, const uint16_t *m, uint16_t m0i); + +void br_i15_decode_reduce(uint16_t *x, + const void *src, size_t len, const uint16_t *m); + +void br_i15_reduce(uint16_t *x, const uint16_t *a, const uint16_t *m); + +void br_i15_mulacc(uint16_t *d, const uint16_t *a, const uint16_t *b); + +uint32_t br_i15_moddiv(uint16_t *x, const uint16_t *y, + const uint16_t *m, uint16_t m0i, uint16_t *t); + +/* + * Variant of br_i31_modpow_opt() that internally uses 64x64->128 + * multiplications. It expects the same parameters as br_i31_modpow_opt(), + * except that the temporaries should be 64-bit integers, not 32-bit + * integers. + */ +uint32_t br_i62_modpow_opt(uint32_t *x31, const unsigned char *e, size_t elen, + const uint32_t *m31, uint32_t m0i31, uint64_t *tmp, size_t twlen); + +/* + * Type for a function with the same API as br_i31_modpow_opt() (some + * implementations of this type may have stricter alignment requirements + * on the temporaries). + */ +typedef uint32_t (*br_i31_modpow_opt_type)(uint32_t *x, + const unsigned char *e, size_t elen, + const uint32_t *m, uint32_t m0i, uint32_t *tmp, size_t twlen); + +/* + * Wrapper for br_i62_modpow_opt() that uses the same type as + * br_i31_modpow_opt(); however, it requires its 'tmp' argument to the + * 64-bit aligned. + */ +uint32_t br_i62_modpow_opt_as_i31(uint32_t *x, + const unsigned char *e, size_t elen, + const uint32_t *m, uint32_t m0i, uint32_t *tmp, size_t twlen); + +/* ==================================================================== */ + +static inline size_t +br_digest_size(const br_hash_class *digest_class) +{ + return (size_t)(digest_class->desc >> BR_HASHDESC_OUT_OFF) + & BR_HASHDESC_OUT_MASK; +} + +/* + * Get the output size (in bytes) of a hash function. + */ +size_t br_digest_size_by_ID(int digest_id); + +/* + * Get the OID (encoded OBJECT IDENTIFIER value, without tag and length) + * for a hash function. If digest_id is not a supported digest identifier + * (in particular if it is equal to 0, i.e. br_md5sha1_ID), then NULL is + * returned and *len is set to 0. + */ +const unsigned char *br_digest_OID(int digest_id, size_t *len); + +/* ==================================================================== */ +/* + * DES support functions. + */ + +/* + * Apply DES Initial Permutation. + */ +void br_des_do_IP(uint32_t *xl, uint32_t *xr); + +/* + * Apply DES Final Permutation (inverse of IP). + */ +void br_des_do_invIP(uint32_t *xl, uint32_t *xr); + +/* + * Key schedule unit: for a DES key (8 bytes), compute 16 subkeys. Each + * subkey is two 28-bit words represented as two 32-bit words; the PC-2 + * bit extration is NOT applied. + */ +void br_des_keysched_unit(uint32_t *skey, const void *key); + +/* + * Reversal of 16 DES sub-keys (for decryption). + */ +void br_des_rev_skey(uint32_t *skey); + +/* + * DES/3DES key schedule for 'des_tab' (encryption direction). Returned + * value is the number of rounds. + */ +unsigned br_des_tab_keysched(uint32_t *skey, const void *key, size_t key_len); + +/* + * DES/3DES key schedule for 'des_ct' (encryption direction). Returned + * value is the number of rounds. + */ +unsigned br_des_ct_keysched(uint32_t *skey, const void *key, size_t key_len); + +/* + * DES/3DES subkey decompression (from the compressed bitsliced subkeys). + */ +void br_des_ct_skey_expand(uint32_t *sk_exp, + unsigned num_rounds, const uint32_t *skey); + +/* + * DES/3DES block encryption/decryption ('des_tab'). + */ +void br_des_tab_process_block(unsigned num_rounds, + const uint32_t *skey, void *block); + +/* + * DES/3DES block encryption/decryption ('des_ct'). + */ +void br_des_ct_process_block(unsigned num_rounds, + const uint32_t *skey, void *block); + +/* ==================================================================== */ +/* + * AES support functions. + */ + +/* + * The AES S-box (256-byte table). + */ +extern const unsigned char br_aes_S[]; + +/* + * AES key schedule. skey[] is filled with n+1 128-bit subkeys, where n + * is the number of rounds (10 to 14, depending on key size). The number + * of rounds is returned. If the key size is invalid (not 16, 24 or 32), + * then 0 is returned. + * + * This implementation uses a 256-byte table and is NOT constant-time. + */ +unsigned br_aes_keysched(uint32_t *skey, const void *key, size_t key_len); + +/* + * AES key schedule for decryption ('aes_big' implementation). + */ +unsigned br_aes_big_keysched_inv(uint32_t *skey, + const void *key, size_t key_len); + +/* + * AES block encryption with the 'aes_big' implementation (fast, but + * not constant-time). This function encrypts a single block "in place". + */ +void br_aes_big_encrypt(unsigned num_rounds, const uint32_t *skey, void *data); + +/* + * AES block decryption with the 'aes_big' implementation (fast, but + * not constant-time). This function decrypts a single block "in place". + */ +void br_aes_big_decrypt(unsigned num_rounds, const uint32_t *skey, void *data); + +/* + * AES block encryption with the 'aes_small' implementation (small, but + * slow and not constant-time). This function encrypts a single block + * "in place". + */ +void br_aes_small_encrypt(unsigned num_rounds, + const uint32_t *skey, void *data); + +/* + * AES block decryption with the 'aes_small' implementation (small, but + * slow and not constant-time). This function decrypts a single block + * "in place". + */ +void br_aes_small_decrypt(unsigned num_rounds, + const uint32_t *skey, void *data); + +/* + * The constant-time implementation is "bitsliced": the 128-bit state is + * split over eight 32-bit words q* in the following way: + * + * -- Input block consists in 16 bytes: + * a00 a10 a20 a30 a01 a11 a21 a31 a02 a12 a22 a32 a03 a13 a23 a33 + * In the terminology of FIPS 197, this is a 4x4 matrix which is read + * column by column. + * + * -- Each byte is split into eight bits which are distributed over the + * eight words, at the same rank. Thus, for a byte x at rank k, bit 0 + * (least significant) of x will be at rank k in q0 (if that bit is b, + * then it contributes "b << k" to the value of q0), bit 1 of x will be + * at rank k in q1, and so on. + * + * -- Ranks given to bits are in "row order" and are either all even, or + * all odd. Two independent AES states are thus interleaved, one using + * the even ranks, the other the odd ranks. Row order means: + * a00 a01 a02 a03 a10 a11 a12 a13 a20 a21 a22 a23 a30 a31 a32 a33 + * + * Converting input bytes from two AES blocks to bitslice representation + * is done in the following way: + * -- Decode first block into the four words q0 q2 q4 q6, in that order, + * using little-endian convention. + * -- Decode second block into the four words q1 q3 q5 q7, in that order, + * using little-endian convention. + * -- Call br_aes_ct_ortho(). + * + * Converting back to bytes is done by using the reverse operations. Note + * that br_aes_ct_ortho() is its own inverse. + */ + +/* + * Perform bytewise orthogonalization of eight 32-bit words. Bytes + * of q0..q7 are spread over all words: for a byte x that occurs + * at rank i in q[j] (byte x uses bits 8*i to 8*i+7 in q[j]), the bit + * of rank k in x (0 <= k <= 7) goes to q[k] at rank 8*i+j. + * + * This operation is an involution. + */ +void br_aes_ct_ortho(uint32_t *q); + +/* + * The AES S-box, as a bitsliced constant-time version. The input array + * consists in eight 32-bit words; 32 S-box instances are computed in + * parallel. Bits 0 to 7 of each S-box input (bit 0 is least significant) + * are spread over the words 0 to 7, at the same rank. + */ +void br_aes_ct_bitslice_Sbox(uint32_t *q); + +/* + * Like br_aes_bitslice_Sbox(), but for the inverse S-box. + */ +void br_aes_ct_bitslice_invSbox(uint32_t *q); + +/* + * Compute AES encryption on bitsliced data. Since input is stored on + * eight 32-bit words, two block encryptions are actually performed + * in parallel. + */ +void br_aes_ct_bitslice_encrypt(unsigned num_rounds, + const uint32_t *skey, uint32_t *q); + +/* + * Compute AES decryption on bitsliced data. Since input is stored on + * eight 32-bit words, two block decryptions are actually performed + * in parallel. + */ +void br_aes_ct_bitslice_decrypt(unsigned num_rounds, + const uint32_t *skey, uint32_t *q); + +/* + * AES key schedule, constant-time version. skey[] is filled with n+1 + * 128-bit subkeys, where n is the number of rounds (10 to 14, depending + * on key size). The number of rounds is returned. If the key size is + * invalid (not 16, 24 or 32), then 0 is returned. + */ +unsigned br_aes_ct_keysched(uint32_t *comp_skey, + const void *key, size_t key_len); + +/* + * Expand AES subkeys as produced by br_aes_ct_keysched(), into + * a larger array suitable for br_aes_ct_bitslice_encrypt() and + * br_aes_ct_bitslice_decrypt(). + */ +void br_aes_ct_skey_expand(uint32_t *skey, + unsigned num_rounds, const uint32_t *comp_skey); + +/* + * For the ct64 implementation, the same bitslicing technique is used, + * but four instances are interleaved. First instance uses bits 0, 4, + * 8, 12,... of each word; second instance uses bits 1, 5, 9, 13,... + * and so on. + */ + +/* + * Perform bytewise orthogonalization of eight 64-bit words. Bytes + * of q0..q7 are spread over all words: for a byte x that occurs + * at rank i in q[j] (byte x uses bits 8*i to 8*i+7 in q[j]), the bit + * of rank k in x (0 <= k <= 7) goes to q[k] at rank 8*i+j. + * + * This operation is an involution. + */ +void br_aes_ct64_ortho(uint64_t *q); + +/* + * Interleave bytes for an AES input block. If input bytes are + * denoted 0123456789ABCDEF, and have been decoded with little-endian + * convention (w[0] contains 0123, with '3' being most significant; + * w[1] contains 4567, and so on), then output word q0 will be + * set to 08192A3B (again little-endian convention) and q1 will + * be set to 4C5D6E7F. + */ +void br_aes_ct64_interleave_in(uint64_t *q0, uint64_t *q1, const uint32_t *w); + +/* + * Perform the opposite of br_aes_ct64_interleave_in(). + */ +void br_aes_ct64_interleave_out(uint32_t *w, uint64_t q0, uint64_t q1); + +/* + * The AES S-box, as a bitsliced constant-time version. The input array + * consists in eight 64-bit words; 64 S-box instances are computed in + * parallel. Bits 0 to 7 of each S-box input (bit 0 is least significant) + * are spread over the words 0 to 7, at the same rank. + */ +void br_aes_ct64_bitslice_Sbox(uint64_t *q); + +/* + * Like br_aes_bitslice_Sbox(), but for the inverse S-box. + */ +void br_aes_ct64_bitslice_invSbox(uint64_t *q); + +/* + * Compute AES encryption on bitsliced data. Since input is stored on + * eight 64-bit words, four block encryptions are actually performed + * in parallel. + */ +void br_aes_ct64_bitslice_encrypt(unsigned num_rounds, + const uint64_t *skey, uint64_t *q); + +/* + * Compute AES decryption on bitsliced data. Since input is stored on + * eight 64-bit words, four block decryptions are actually performed + * in parallel. + */ +void br_aes_ct64_bitslice_decrypt(unsigned num_rounds, + const uint64_t *skey, uint64_t *q); + +/* + * AES key schedule, constant-time version. skey[] is filled with n+1 + * 128-bit subkeys, where n is the number of rounds (10 to 14, depending + * on key size). The number of rounds is returned. If the key size is + * invalid (not 16, 24 or 32), then 0 is returned. + */ +unsigned br_aes_ct64_keysched(uint64_t *comp_skey, + const void *key, size_t key_len); + +/* + * Expand AES subkeys as produced by br_aes_ct64_keysched(), into + * a larger array suitable for br_aes_ct64_bitslice_encrypt() and + * br_aes_ct64_bitslice_decrypt(). + */ +void br_aes_ct64_skey_expand(uint64_t *skey, + unsigned num_rounds, const uint64_t *comp_skey); + +/* + * Test support for AES-NI opcodes. + */ +int br_aes_x86ni_supported(void); + +/* + * AES key schedule, using x86 AES-NI instructions. This yields the + * subkeys in the encryption direction. Number of rounds is returned. + * Key size MUST be 16, 24 or 32 bytes; otherwise, 0 is returned. + */ +unsigned br_aes_x86ni_keysched_enc(unsigned char *skni, + const void *key, size_t len); + +/* + * AES key schedule, using x86 AES-NI instructions. This yields the + * subkeys in the decryption direction. Number of rounds is returned. + * Key size MUST be 16, 24 or 32 bytes; otherwise, 0 is returned. + */ +unsigned br_aes_x86ni_keysched_dec(unsigned char *skni, + const void *key, size_t len); + +/* + * Test support for AES POWER8 opcodes. + */ +int br_aes_pwr8_supported(void); + +/* + * AES key schedule, using POWER8 instructions. This yields the + * subkeys in the encryption direction. Number of rounds is returned. + * Key size MUST be 16, 24 or 32 bytes; otherwise, 0 is returned. + */ +unsigned br_aes_pwr8_keysched(unsigned char *skni, + const void *key, size_t len); + +/* ==================================================================== */ +/* + * RSA. + */ + +/* + * Apply proper PKCS#1 v1.5 padding (for signatures). 'hash_oid' is + * the encoded hash function OID, or NULL. + */ +uint32_t br_rsa_pkcs1_sig_pad(const unsigned char *hash_oid, + const unsigned char *hash, size_t hash_len, + uint32_t n_bitlen, unsigned char *x); + +/* + * Check PKCS#1 v1.5 padding (for signatures). 'hash_oid' is the encoded + * hash function OID, or NULL. The provided 'sig' value is _after_ the + * modular exponentiation, i.e. it should be the padded hash. On + * success, the hashed message is extracted. + */ +uint32_t br_rsa_pkcs1_sig_unpad(const unsigned char *sig, size_t sig_len, + const unsigned char *hash_oid, size_t hash_len, + unsigned char *hash_out); + +/* + * Apply proper PSS padding. The 'x' buffer is output only: it + * receives the value that is to be exponentiated. + */ +uint32_t br_rsa_pss_sig_pad(const br_prng_class **rng, + const br_hash_class *hf_data, const br_hash_class *hf_mgf1, + const unsigned char *hash, size_t salt_len, + uint32_t n_bitlen, unsigned char *x); + +/* + * Check PSS padding. The provided value is the one _after_ + * the modular exponentiation; it is modified by this function. + * This function infers the signature length from the public key + * size, i.e. it assumes that this has already been verified (as + * part of the exponentiation). + */ +uint32_t br_rsa_pss_sig_unpad( + const br_hash_class *hf_data, const br_hash_class *hf_mgf1, + const unsigned char *hash, size_t salt_len, + const br_rsa_public_key *pk, unsigned char *x); + +/* + * Apply OAEP padding. Returned value is the actual padded string length, + * or zero on error. + */ +size_t br_rsa_oaep_pad(const br_prng_class **rnd, const br_hash_class *dig, + const void *label, size_t label_len, const br_rsa_public_key *pk, + void *dst, size_t dst_nax_len, const void *src, size_t src_len); + +/* + * Unravel and check OAEP padding. If the padding is correct, then 1 is + * returned, '*len' is adjusted to the length of the message, and the + * data is moved to the start of the 'data' buffer. If the padding is + * incorrect, then 0 is returned and '*len' is untouched. Either way, + * the complete buffer contents are altered. + */ +uint32_t br_rsa_oaep_unpad(const br_hash_class *dig, + const void *label, size_t label_len, void *data, size_t *len); + +/* + * Compute MGF1 for a given seed, and XOR the output into the provided + * buffer. + */ +void br_mgf1_xor(void *data, size_t len, + const br_hash_class *dig, const void *seed, size_t seed_len); + +/* + * Inner function for RSA key generation; used by the "i31" and "i62" + * implementations. + */ +uint32_t br_rsa_i31_keygen_inner(const br_prng_class **rng, + br_rsa_private_key *sk, void *kbuf_priv, + br_rsa_public_key *pk, void *kbuf_pub, + unsigned size, uint32_t pubexp, br_i31_modpow_opt_type mp31); + +/* ==================================================================== */ +/* + * Elliptic curves. + */ + +/* + * Type for generic EC parameters: curve order (unsigned big-endian + * encoding) and encoded conventional generator. + */ +typedef struct { + int curve; + const unsigned char *order; + size_t order_len; + const unsigned char *generator; + size_t generator_len; +} br_ec_curve_def; + +extern const br_ec_curve_def br_secp256r1; +extern const br_ec_curve_def br_secp384r1; +extern const br_ec_curve_def br_secp521r1; + +/* + * For Curve25519, the advertised "order" really is 2^255-1, since the + * point multipliction function really works over arbitrary 255-bit + * scalars. This value is only meant as a hint for ECDH key generation; + * only ECDSA uses the exact curve order, and ECDSA is not used with + * that specific curve. + */ +extern const br_ec_curve_def br_curve25519; + +/* + * Decode some bytes as an i31 integer, with truncation (corresponding + * to the 'bits2int' operation in RFC 6979). The target ENCODED bit + * length is provided as last parameter. The resulting value will have + * this declared bit length, and consists the big-endian unsigned decoding + * of exactly that many bits in the source (capped at the source length). + */ +void br_ecdsa_i31_bits2int(uint32_t *x, + const void *src, size_t len, uint32_t ebitlen); + +/* + * Decode some bytes as an i15 integer, with truncation (corresponding + * to the 'bits2int' operation in RFC 6979). The target ENCODED bit + * length is provided as last parameter. The resulting value will have + * this declared bit length, and consists the big-endian unsigned decoding + * of exactly that many bits in the source (capped at the source length). + */ +void br_ecdsa_i15_bits2int(uint16_t *x, + const void *src, size_t len, uint32_t ebitlen); + +/* ==================================================================== */ +/* + * ASN.1 support functions. + */ + +/* + * A br_asn1_uint structure contains encoding information about an + * INTEGER nonnegative value: pointer to the integer contents (unsigned + * big-endian representation), length of the integer contents, + * and length of the encoded value. The data shall have minimal length: + * - If the integer value is zero, then 'len' must be zero. + * - If the integer value is not zero, then data[0] must be non-zero. + * + * Under these conditions, 'asn1len' is necessarily equal to either len + * or len+1. + */ +typedef struct { + const unsigned char *data; + size_t len; + size_t asn1len; +} br_asn1_uint; + +/* + * Given an encoded integer (unsigned big-endian, with possible leading + * bytes of value 0), returned the "prepared INTEGER" structure. + */ +br_asn1_uint br_asn1_uint_prepare(const void *xdata, size_t xlen); + +/* + * Encode an ASN.1 length. The length of the encoded length is returned. + * If 'dest' is NULL, then no encoding is performed, but the length of + * the encoded length is still computed and returned. + */ +size_t br_asn1_encode_length(void *dest, size_t len); + +/* + * Convenient macro for computing lengths of lengths. + */ +#define len_of_len(len) br_asn1_encode_length(NULL, len) + +/* + * Encode a (prepared) ASN.1 INTEGER. The encoded length is returned. + * If 'dest' is NULL, then no encoding is performed, but the length of + * the encoded integer is still computed and returned. + */ +size_t br_asn1_encode_uint(void *dest, br_asn1_uint pp); + +/* + * Get the OID that identifies an elliptic curve. Returned value is + * the DER-encoded OID, with the length (always one byte) but without + * the tag. Thus, the first byte of the returned buffer contains the + * number of subsequent bytes in the value. If the curve is not + * recognised, NULL is returned. + */ +const unsigned char *br_get_curve_OID(int curve); + +/* + * Inner function for EC private key encoding. This is equivalent to + * the API function br_encode_ec_raw_der(), except for an extra + * parameter: if 'include_curve_oid' is zero, then the curve OID is + * _not_ included in the output blob (this is for PKCS#8 support). + */ +size_t br_encode_ec_raw_der_inner(void *dest, + const br_ec_private_key *sk, const br_ec_public_key *pk, + int include_curve_oid); + +/* ==================================================================== */ +/* + * SSL/TLS support functions. + */ + +/* + * Record types. + */ +#define BR_SSL_CHANGE_CIPHER_SPEC 20 +#define BR_SSL_ALERT 21 +#define BR_SSL_HANDSHAKE 22 +#define BR_SSL_APPLICATION_DATA 23 + +/* + * Handshake message types. + */ +#define BR_SSL_HELLO_REQUEST 0 +#define BR_SSL_CLIENT_HELLO 1 +#define BR_SSL_SERVER_HELLO 2 +#define BR_SSL_CERTIFICATE 11 +#define BR_SSL_SERVER_KEY_EXCHANGE 12 +#define BR_SSL_CERTIFICATE_REQUEST 13 +#define BR_SSL_SERVER_HELLO_DONE 14 +#define BR_SSL_CERTIFICATE_VERIFY 15 +#define BR_SSL_CLIENT_KEY_EXCHANGE 16 +#define BR_SSL_FINISHED 20 + +/* + * Alert levels. + */ +#define BR_LEVEL_WARNING 1 +#define BR_LEVEL_FATAL 2 + +/* + * Low-level I/O state. + */ +#define BR_IO_FAILED 0 +#define BR_IO_IN 1 +#define BR_IO_OUT 2 +#define BR_IO_INOUT 3 + +/* + * Mark a SSL engine as failed. The provided error code is recorded if + * the engine was not already marked as failed. If 'err' is 0, then the + * engine is marked as closed (without error). + */ +void br_ssl_engine_fail(br_ssl_engine_context *cc, int err); + +/* + * Test whether the engine is closed (normally or as a failure). + */ +static inline int +br_ssl_engine_closed(const br_ssl_engine_context *cc) +{ + return cc->iomode == BR_IO_FAILED; +} + +/* + * Configure a new maximum fragment length. If possible, the maximum + * length for outgoing records is immediately adjusted (if there are + * not already too many buffered bytes for that). + */ +void br_ssl_engine_new_max_frag_len( + br_ssl_engine_context *rc, unsigned max_frag_len); + +/* + * Test whether the current incoming record has been fully received + * or not. This functions returns 0 only if a complete record header + * has been received, but some of the (possibly encrypted) payload + * has not yet been obtained. + */ +int br_ssl_engine_recvrec_finished(const br_ssl_engine_context *rc); + +/* + * Flush the current record (if not empty). This is meant to be called + * from the handshake processor only. + */ +void br_ssl_engine_flush_record(br_ssl_engine_context *cc); + +/* + * Test whether there is some accumulated payload to send. + */ +static inline int +br_ssl_engine_has_pld_to_send(const br_ssl_engine_context *rc) +{ + return rc->oxa != rc->oxb && rc->oxa != rc->oxc; +} + +/* + * Initialize RNG in engine. Returned value is 1 on success, 0 on error. + * This function will try to use the OS-provided RNG, if available. If + * there is no OS-provided RNG, or if it failed, and no entropy was + * injected by the caller, then a failure will be reported. On error, + * the context error code is set. + */ +int br_ssl_engine_init_rand(br_ssl_engine_context *cc); + +/* + * Reset the handshake-related parts of the engine. + */ +void br_ssl_engine_hs_reset(br_ssl_engine_context *cc, + void (*hsinit)(void *), void (*hsrun)(void *)); + +/* + * Get the PRF to use for this context, for the provided PRF hash + * function ID. + */ +br_tls_prf_impl br_ssl_engine_get_PRF(br_ssl_engine_context *cc, int prf_id); + +/* + * Consume the provided pre-master secret and compute the corresponding + * master secret. The 'prf_id' is the ID of the hash function to use + * with the TLS 1.2 PRF (ignored if the version is TLS 1.0 or 1.1). + */ +void br_ssl_engine_compute_master(br_ssl_engine_context *cc, + int prf_id, const void *pms, size_t len); + +/* + * Switch to CBC decryption for incoming records. + * cc the engine context + * is_client non-zero for a client, zero for a server + * prf_id id of hash function for PRF (ignored if not TLS 1.2+) + * mac_id id of hash function for HMAC + * bc_impl block cipher implementation (CBC decryption) + * cipher_key_len block cipher key length (in bytes) + */ +void br_ssl_engine_switch_cbc_in(br_ssl_engine_context *cc, + int is_client, int prf_id, int mac_id, + const br_block_cbcdec_class *bc_impl, size_t cipher_key_len); + +/* + * Switch to CBC encryption for outgoing records. + * cc the engine context + * is_client non-zero for a client, zero for a server + * prf_id id of hash function for PRF (ignored if not TLS 1.2+) + * mac_id id of hash function for HMAC + * bc_impl block cipher implementation (CBC encryption) + * cipher_key_len block cipher key length (in bytes) + */ +void br_ssl_engine_switch_cbc_out(br_ssl_engine_context *cc, + int is_client, int prf_id, int mac_id, + const br_block_cbcenc_class *bc_impl, size_t cipher_key_len); + +/* + * Switch to GCM decryption for incoming records. + * cc the engine context + * is_client non-zero for a client, zero for a server + * prf_id id of hash function for PRF + * bc_impl block cipher implementation (CTR) + * cipher_key_len block cipher key length (in bytes) + */ +void br_ssl_engine_switch_gcm_in(br_ssl_engine_context *cc, + int is_client, int prf_id, + const br_block_ctr_class *bc_impl, size_t cipher_key_len); + +/* + * Switch to GCM encryption for outgoing records. + * cc the engine context + * is_client non-zero for a client, zero for a server + * prf_id id of hash function for PRF + * bc_impl block cipher implementation (CTR) + * cipher_key_len block cipher key length (in bytes) + */ +void br_ssl_engine_switch_gcm_out(br_ssl_engine_context *cc, + int is_client, int prf_id, + const br_block_ctr_class *bc_impl, size_t cipher_key_len); + +/* + * Switch to ChaCha20+Poly1305 decryption for incoming records. + * cc the engine context + * is_client non-zero for a client, zero for a server + * prf_id id of hash function for PRF + */ +void br_ssl_engine_switch_chapol_in(br_ssl_engine_context *cc, + int is_client, int prf_id); + +/* + * Switch to ChaCha20+Poly1305 encryption for outgoing records. + * cc the engine context + * is_client non-zero for a client, zero for a server + * prf_id id of hash function for PRF + */ +void br_ssl_engine_switch_chapol_out(br_ssl_engine_context *cc, + int is_client, int prf_id); + +/* + * Switch to CCM decryption for incoming records. + * cc the engine context + * is_client non-zero for a client, zero for a server + * prf_id id of hash function for PRF + * bc_impl block cipher implementation (CTR+CBC) + * cipher_key_len block cipher key length (in bytes) + * tag_len tag length (in bytes) + */ +void br_ssl_engine_switch_ccm_in(br_ssl_engine_context *cc, + int is_client, int prf_id, + const br_block_ctrcbc_class *bc_impl, + size_t cipher_key_len, size_t tag_len); + +/* + * Switch to GCM encryption for outgoing records. + * cc the engine context + * is_client non-zero for a client, zero for a server + * prf_id id of hash function for PRF + * bc_impl block cipher implementation (CTR+CBC) + * cipher_key_len block cipher key length (in bytes) + * tag_len tag length (in bytes) + */ +void br_ssl_engine_switch_ccm_out(br_ssl_engine_context *cc, + int is_client, int prf_id, + const br_block_ctrcbc_class *bc_impl, + size_t cipher_key_len, size_t tag_len); + +/* + * Calls to T0-generated code. + */ +void br_ssl_hs_client_init_main(void *ctx); +void br_ssl_hs_client_run(void *ctx); +void br_ssl_hs_server_init_main(void *ctx); +void br_ssl_hs_server_run(void *ctx); + +/* + * Get the hash function to use for signatures, given a bit mask of + * supported hash functions. This implements a strict choice order + * (namely SHA-256, SHA-384, SHA-512, SHA-224, SHA-1). If the mask + * does not document support of any of these hash functions, then this + * functions returns 0. + */ +int br_ssl_choose_hash(unsigned bf); + +/* ==================================================================== */ + +/* + * PowerPC / POWER assembly stuff. The special BR_POWER_ASM_MACROS macro + * must be defined before including this file; this is done by source + * files that use some inline assembly for PowerPC / POWER machines. + */ + +#if BR_POWER_ASM_MACROS + +#define lxvw4x(xt, ra, rb) lxvw4x_(xt, ra, rb) +#define stxvw4x(xt, ra, rb) stxvw4x_(xt, ra, rb) + +#define bdnz(foo) bdnz_(foo) +#define bdz(foo) bdz_(foo) +#define beq(foo) beq_(foo) + +#define li(rx, value) li_(rx, value) +#define addi(rx, ra, imm) addi_(rx, ra, imm) +#define cmpldi(rx, imm) cmpldi_(rx, imm) +#define mtctr(rx) mtctr_(rx) +#define vspltb(vrt, vrb, uim) vspltb_(vrt, vrb, uim) +#define vspltw(vrt, vrb, uim) vspltw_(vrt, vrb, uim) +#define vspltisb(vrt, imm) vspltisb_(vrt, imm) +#define vspltisw(vrt, imm) vspltisw_(vrt, imm) +#define vrlw(vrt, vra, vrb) vrlw_(vrt, vra, vrb) +#define vsbox(vrt, vra) vsbox_(vrt, vra) +#define vxor(vrt, vra, vrb) vxor_(vrt, vra, vrb) +#define vand(vrt, vra, vrb) vand_(vrt, vra, vrb) +#define vsro(vrt, vra, vrb) vsro_(vrt, vra, vrb) +#define vsl(vrt, vra, vrb) vsl_(vrt, vra, vrb) +#define vsldoi(vt, va, vb, sh) vsldoi_(vt, va, vb, sh) +#define vsr(vrt, vra, vrb) vsr_(vrt, vra, vrb) +#define vaddcuw(vrt, vra, vrb) vaddcuw_(vrt, vra, vrb) +#define vadduwm(vrt, vra, vrb) vadduwm_(vrt, vra, vrb) +#define vsububm(vrt, vra, vrb) vsububm_(vrt, vra, vrb) +#define vsubuwm(vrt, vra, vrb) vsubuwm_(vrt, vra, vrb) +#define vsrw(vrt, vra, vrb) vsrw_(vrt, vra, vrb) +#define vcipher(vt, va, vb) vcipher_(vt, va, vb) +#define vcipherlast(vt, va, vb) vcipherlast_(vt, va, vb) +#define vncipher(vt, va, vb) vncipher_(vt, va, vb) +#define vncipherlast(vt, va, vb) vncipherlast_(vt, va, vb) +#define vperm(vt, va, vb, vc) vperm_(vt, va, vb, vc) +#define vpmsumd(vt, va, vb) vpmsumd_(vt, va, vb) +#define xxpermdi(vt, va, vb, d) xxpermdi_(vt, va, vb, d) + +#define lxvw4x_(xt, ra, rb) "\tlxvw4x\t" #xt "," #ra "," #rb "\n" +#define stxvw4x_(xt, ra, rb) "\tstxvw4x\t" #xt "," #ra "," #rb "\n" + +#define label(foo) #foo "%=:\n" +#define bdnz_(foo) "\tbdnz\t" #foo "%=\n" +#define bdz_(foo) "\tbdz\t" #foo "%=\n" +#define beq_(foo) "\tbeq\t" #foo "%=\n" + +#define li_(rx, value) "\tli\t" #rx "," #value "\n" +#define addi_(rx, ra, imm) "\taddi\t" #rx "," #ra "," #imm "\n" +#define cmpldi_(rx, imm) "\tcmpldi\t" #rx "," #imm "\n" +#define mtctr_(rx) "\tmtctr\t" #rx "\n" +#define vspltb_(vrt, vrb, uim) "\tvspltb\t" #vrt "," #vrb "," #uim "\n" +#define vspltw_(vrt, vrb, uim) "\tvspltw\t" #vrt "," #vrb "," #uim "\n" +#define vspltisb_(vrt, imm) "\tvspltisb\t" #vrt "," #imm "\n" +#define vspltisw_(vrt, imm) "\tvspltisw\t" #vrt "," #imm "\n" +#define vrlw_(vrt, vra, vrb) "\tvrlw\t" #vrt "," #vra "," #vrb "\n" +#define vsbox_(vrt, vra) "\tvsbox\t" #vrt "," #vra "\n" +#define vxor_(vrt, vra, vrb) "\tvxor\t" #vrt "," #vra "," #vrb "\n" +#define vand_(vrt, vra, vrb) "\tvand\t" #vrt "," #vra "," #vrb "\n" +#define vsro_(vrt, vra, vrb) "\tvsro\t" #vrt "," #vra "," #vrb "\n" +#define vsl_(vrt, vra, vrb) "\tvsl\t" #vrt "," #vra "," #vrb "\n" +#define vsldoi_(vt, va, vb, sh) "\tvsldoi\t" #vt "," #va "," #vb "," #sh "\n" +#define vsr_(vrt, vra, vrb) "\tvsr\t" #vrt "," #vra "," #vrb "\n" +#define vaddcuw_(vrt, vra, vrb) "\tvaddcuw\t" #vrt "," #vra "," #vrb "\n" +#define vadduwm_(vrt, vra, vrb) "\tvadduwm\t" #vrt "," #vra "," #vrb "\n" +#define vsububm_(vrt, vra, vrb) "\tvsububm\t" #vrt "," #vra "," #vrb "\n" +#define vsubuwm_(vrt, vra, vrb) "\tvsubuwm\t" #vrt "," #vra "," #vrb "\n" +#define vsrw_(vrt, vra, vrb) "\tvsrw\t" #vrt "," #vra "," #vrb "\n" +#define vcipher_(vt, va, vb) "\tvcipher\t" #vt "," #va "," #vb "\n" +#define vcipherlast_(vt, va, vb) "\tvcipherlast\t" #vt "," #va "," #vb "\n" +#define vncipher_(vt, va, vb) "\tvncipher\t" #vt "," #va "," #vb "\n" +#define vncipherlast_(vt, va, vb) "\tvncipherlast\t" #vt "," #va "," #vb "\n" +#define vperm_(vt, va, vb, vc) "\tvperm\t" #vt "," #va "," #vb "," #vc "\n" +#define vpmsumd_(vt, va, vb) "\tvpmsumd\t" #vt "," #va "," #vb "\n" +#define xxpermdi_(vt, va, vb, d) "\txxpermdi\t" #vt "," #va "," #vb "," #d "\n" + +#endif + +/* ==================================================================== */ +/* + * Special "activate intrinsics" code, needed for some compiler versions. + * This is defined at the end of this file, so that it won't impact any + * of the inline functions defined previously; and it is controlled by + * a specific macro defined in the caller code. + * + * Calling code conventions: + * + * - Caller must define BR_ENABLE_INTRINSICS before including "inner.h". + * - Functions that use intrinsics must be enclosed in an "enabled" + * region (between BR_TARGETS_X86_UP and BR_TARGETS_X86_DOWN). + * - Functions that use intrinsics must be tagged with the appropriate + * BR_TARGET(). + */ + +#if BR_ENABLE_INTRINSICS && (BR_GCC_4_4 || BR_CLANG_3_7 || BR_MSC_2005) + +/* + * x86 intrinsics (both 32-bit and 64-bit). + */ +#if BR_i386 || BR_amd64 + +/* + * On GCC before version 5.0, we need to use the pragma to enable the + * target options globally, because the 'target' function attribute + * appears to be unreliable. Before 4.6 we must also avoid the + * push_options / pop_options mechanism, because it tends to trigger + * some internal compiler errors. + */ +#if BR_GCC && !BR_GCC_5_0 +#if BR_GCC_4_6 +#define BR_TARGETS_X86_UP \ + _Pragma("GCC push_options") \ + _Pragma("GCC target(\"sse2,ssse3,sse4.1,aes,pclmul,rdrnd\")") +#define BR_TARGETS_X86_DOWN \ + _Pragma("GCC pop_options") +#else +#define BR_TARGETS_X86_UP \ + _Pragma("GCC target(\"sse2,ssse3,sse4.1,aes,pclmul\")") +#define BR_TARGETS_X86_DOWN +#endif +#pragma GCC diagnostic ignored "-Wpsabi" +#endif + +#if BR_CLANG && !BR_CLANG_3_8 +#undef __SSE2__ +#undef __SSE3__ +#undef __SSSE3__ +#undef __SSE4_1__ +#undef __AES__ +#undef __PCLMUL__ +#undef __RDRND__ +#define __SSE2__ 1 +#define __SSE3__ 1 +#define __SSSE3__ 1 +#define __SSE4_1__ 1 +#define __AES__ 1 +#define __PCLMUL__ 1 +#define __RDRND__ 1 +#endif + +#ifndef BR_TARGETS_X86_UP +#define BR_TARGETS_X86_UP +#endif +#ifndef BR_TARGETS_X86_DOWN +#define BR_TARGETS_X86_DOWN +#endif + +#if BR_GCC || BR_CLANG +BR_TARGETS_X86_UP +#include +#include +#define br_bswap32 __builtin_bswap32 +BR_TARGETS_X86_DOWN +#endif + +#if BR_MSC +#include +#include +#include +#define br_bswap32 _byteswap_ulong +#endif + +static inline int +br_cpuid(uint32_t mask_eax, uint32_t mask_ebx, + uint32_t mask_ecx, uint32_t mask_edx) +{ +#if BR_CPUID +#if BR_GCC || BR_CLANG + unsigned eax, ebx, ecx, edx; + + if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) { + if ((eax & mask_eax) == mask_eax + && (ebx & mask_ebx) == mask_ebx + && (ecx & mask_ecx) == mask_ecx + && (edx & mask_edx) == mask_edx) + { + return 1; + } + } +#elif BR_MSC + int info[4]; + + __cpuid(info, 1); + if (((uint32_t)info[0] & mask_eax) == mask_eax + && ((uint32_t)info[1] & mask_ebx) == mask_ebx + && ((uint32_t)info[2] & mask_ecx) == mask_ecx + && ((uint32_t)info[3] & mask_edx) == mask_edx) + { + return 1; + } +#endif +#endif + return 0; +} + +#endif + +#endif + +/* ==================================================================== */ + +#endif diff --git a/enclave/sgxsd_enclave/bearssl/sha2small.c b/enclave/sgxsd_enclave/bearssl/sha2small.c new file mode 100644 index 0000000..ca19655 --- /dev/null +++ b/enclave/sgxsd_enclave/bearssl/sha2small.c @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2016 Thomas Pornin + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "inner.h" + +#define CH(X, Y, Z) ((((Y) ^ (Z)) & (X)) ^ (Z)) +#define MAJ(X, Y, Z) (((Y) & (Z)) | (((Y) | (Z)) & (X))) + +#define ROTR(x, n) (((uint32_t)(x) << (32 - (n))) | ((uint32_t)(x) >> (n))) + +#define BSG2_0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define BSG2_1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define SSG2_0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ (uint32_t)((x) >> 3)) +#define SSG2_1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ (uint32_t)((x) >> 10)) + +/* see inner.h */ +const uint32_t br_sha224_IV[8] = { + 0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939, + 0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4 +}; + +/* see inner.h */ +const uint32_t br_sha256_IV[8] = { + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, + 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 +}; + +static const uint32_t K[64] = { + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, + 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, + 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, + 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, + 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, + 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, + 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, + 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, + 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 +}; + +/* see inner.h */ +void +br_sha2small_round(const unsigned char *buf, uint32_t *val) +{ + +#define SHA2_STEP(A, B, C, D, E, F, G, H, j) do { \ + uint32_t T1, T2; \ + T1 = H + BSG2_1(E) + CH(E, F, G) + K[j] + w[j]; \ + T2 = BSG2_0(A) + MAJ(A, B, C); \ + D += T1; \ + H = T1 + T2; \ + } while (0) + + int i; + uint32_t a, b, c, d, e, f, g, h; + uint32_t w[64]; + + br_range_dec32be(w, 16, buf); + for (i = 16; i < 64; i ++) { + w[i] = SSG2_1(w[i - 2]) + w[i - 7] + + SSG2_0(w[i - 15]) + w[i - 16]; + } + a = val[0]; + b = val[1]; + c = val[2]; + d = val[3]; + e = val[4]; + f = val[5]; + g = val[6]; + h = val[7]; + for (i = 0; i < 64; i += 8) { + SHA2_STEP(a, b, c, d, e, f, g, h, i + 0); + SHA2_STEP(h, a, b, c, d, e, f, g, i + 1); + SHA2_STEP(g, h, a, b, c, d, e, f, i + 2); + SHA2_STEP(f, g, h, a, b, c, d, e, i + 3); + SHA2_STEP(e, f, g, h, a, b, c, d, i + 4); + SHA2_STEP(d, e, f, g, h, a, b, c, i + 5); + SHA2_STEP(c, d, e, f, g, h, a, b, i + 6); + SHA2_STEP(b, c, d, e, f, g, h, a, i + 7); + } + val[0] += a; + val[1] += b; + val[2] += c; + val[3] += d; + val[4] += e; + val[5] += f; + val[6] += g; + val[7] += h; + +#if 0 +/* obsolete */ +#define SHA2_MEXP1(pc) do { \ + W[pc] = br_dec32be(buf + ((pc) << 2)); \ + } while (0) + +#define SHA2_MEXP2(pc) do { \ + W[(pc) & 0x0F] = SSG2_1(W[((pc) - 2) & 0x0F]) \ + + W[((pc) - 7) & 0x0F] \ + + SSG2_0(W[((pc) - 15) & 0x0F]) + W[(pc) & 0x0F]; \ + } while (0) + +#define SHA2_STEPn(n, a, b, c, d, e, f, g, h, pc) do { \ + uint32_t t1, t2; \ + SHA2_MEXP ## n(pc); \ + t1 = h + BSG2_1(e) + CH(e, f, g) \ + + K[pcount + (pc)] + W[(pc) & 0x0F]; \ + t2 = BSG2_0(a) + MAJ(a, b, c); \ + d += t1; \ + h = t1 + t2; \ + } while (0) + +#define SHA2_STEP1(a, b, c, d, e, f, g, h, pc) \ + SHA2_STEPn(1, a, b, c, d, e, f, g, h, pc) +#define SHA2_STEP2(a, b, c, d, e, f, g, h, pc) \ + SHA2_STEPn(2, a, b, c, d, e, f, g, h, pc) + + uint32_t A, B, C, D, E, F, G, H; + uint32_t W[16]; + unsigned pcount; + + A = val[0]; + B = val[1]; + C = val[2]; + D = val[3]; + E = val[4]; + F = val[5]; + G = val[6]; + H = val[7]; + pcount = 0; + SHA2_STEP1(A, B, C, D, E, F, G, H, 0); + SHA2_STEP1(H, A, B, C, D, E, F, G, 1); + SHA2_STEP1(G, H, A, B, C, D, E, F, 2); + SHA2_STEP1(F, G, H, A, B, C, D, E, 3); + SHA2_STEP1(E, F, G, H, A, B, C, D, 4); + SHA2_STEP1(D, E, F, G, H, A, B, C, 5); + SHA2_STEP1(C, D, E, F, G, H, A, B, 6); + SHA2_STEP1(B, C, D, E, F, G, H, A, 7); + SHA2_STEP1(A, B, C, D, E, F, G, H, 8); + SHA2_STEP1(H, A, B, C, D, E, F, G, 9); + SHA2_STEP1(G, H, A, B, C, D, E, F, 10); + SHA2_STEP1(F, G, H, A, B, C, D, E, 11); + SHA2_STEP1(E, F, G, H, A, B, C, D, 12); + SHA2_STEP1(D, E, F, G, H, A, B, C, 13); + SHA2_STEP1(C, D, E, F, G, H, A, B, 14); + SHA2_STEP1(B, C, D, E, F, G, H, A, 15); + for (pcount = 16; pcount < 64; pcount += 16) { + SHA2_STEP2(A, B, C, D, E, F, G, H, 0); + SHA2_STEP2(H, A, B, C, D, E, F, G, 1); + SHA2_STEP2(G, H, A, B, C, D, E, F, 2); + SHA2_STEP2(F, G, H, A, B, C, D, E, 3); + SHA2_STEP2(E, F, G, H, A, B, C, D, 4); + SHA2_STEP2(D, E, F, G, H, A, B, C, 5); + SHA2_STEP2(C, D, E, F, G, H, A, B, 6); + SHA2_STEP2(B, C, D, E, F, G, H, A, 7); + SHA2_STEP2(A, B, C, D, E, F, G, H, 8); + SHA2_STEP2(H, A, B, C, D, E, F, G, 9); + SHA2_STEP2(G, H, A, B, C, D, E, F, 10); + SHA2_STEP2(F, G, H, A, B, C, D, E, 11); + SHA2_STEP2(E, F, G, H, A, B, C, D, 12); + SHA2_STEP2(D, E, F, G, H, A, B, C, 13); + SHA2_STEP2(C, D, E, F, G, H, A, B, 14); + SHA2_STEP2(B, C, D, E, F, G, H, A, 15); + } + val[0] += A; + val[1] += B; + val[2] += C; + val[3] += D; + val[4] += E; + val[5] += F; + val[6] += G; + val[7] += H; +#endif +} + +static void +sha2small_update(br_sha224_context *cc, const void *data, size_t len) +{ + const unsigned char *buf; + size_t ptr; + + buf = data; + ptr = (size_t)cc->count & 63; + cc->count += (uint64_t)len; + while (len > 0) { + size_t clen; + + clen = 64 - ptr; + if (clen > len) { + clen = len; + } + memcpy(cc->buf + ptr, buf, clen); + ptr += clen; + buf += clen; + len -= clen; + if (ptr == 64) { + br_sha2small_round(cc->buf, cc->val); + ptr = 0; + } + } +} + +static void +sha2small_out(const br_sha224_context *cc, void *dst, int num) +{ + unsigned char buf[64]; + uint32_t val[8]; + size_t ptr; + + ptr = (size_t)cc->count & 63; + memcpy(buf, cc->buf, ptr); + memcpy(val, cc->val, sizeof val); + buf[ptr ++] = 0x80; + if (ptr > 56) { + memset(buf + ptr, 0, 64 - ptr); + br_sha2small_round(buf, val); + memset(buf, 0, 56); + } else { + memset(buf + ptr, 0, 56 - ptr); + } + br_enc64be(buf + 56, cc->count << 3); + br_sha2small_round(buf, val); + br_range_enc32be(dst, val, num); +} + +/* see bearssl.h */ +void +br_sha224_init(br_sha224_context *cc) +{ + cc->vtable = &br_sha224_vtable; + memcpy(cc->val, br_sha224_IV, sizeof cc->val); + cc->count = 0; +} + +/* see bearssl.h */ +void +br_sha224_update(br_sha224_context *cc, const void *data, size_t len) +{ + sha2small_update(cc, data, len); +} + +/* see bearssl.h */ +void +br_sha224_out(const br_sha224_context *cc, void *dst) +{ + sha2small_out(cc, dst, 7); +} + +/* see bearssl.h */ +uint64_t +br_sha224_state(const br_sha224_context *cc, void *dst) +{ + br_range_enc32be(dst, cc->val, 8); + return cc->count; +} + +/* see bearssl.h */ +void +br_sha224_set_state(br_sha224_context *cc, const void *stb, uint64_t count) +{ + br_range_dec32be(cc->val, 8, stb); + cc->count = count; +} + +/* see bearssl.h */ +void +br_sha256_init(br_sha256_context *cc) +{ + cc->vtable = &br_sha256_vtable; + memcpy(cc->val, br_sha256_IV, sizeof cc->val); + cc->count = 0; +} + +/* see bearssl.h */ +void +br_sha256_out(const br_sha256_context *cc, void *dst) +{ + sha2small_out(cc, dst, 8); +} + +/* see bearssl.h */ +const br_hash_class br_sha224_vtable = { + sizeof(br_sha224_context), + BR_HASHDESC_ID(br_sha224_ID) + | BR_HASHDESC_OUT(28) + | BR_HASHDESC_STATE(32) + | BR_HASHDESC_LBLEN(6) + | BR_HASHDESC_MD_PADDING + | BR_HASHDESC_MD_PADDING_BE, + (void (*)(const br_hash_class **))&br_sha224_init, + (void (*)(const br_hash_class **, + const void *, size_t))&br_sha224_update, + (void (*)(const br_hash_class *const *, void *))&br_sha224_out, + (uint64_t (*)(const br_hash_class *const *, void *))&br_sha224_state, + (void (*)(const br_hash_class **, const void *, uint64_t)) + &br_sha224_set_state +}; + +/* see bearssl.h */ +const br_hash_class br_sha256_vtable = { + sizeof(br_sha256_context), + BR_HASHDESC_ID(br_sha256_ID) + | BR_HASHDESC_OUT(32) + | BR_HASHDESC_STATE(32) + | BR_HASHDESC_LBLEN(6) + | BR_HASHDESC_MD_PADDING + | BR_HASHDESC_MD_PADDING_BE, + (void (*)(const br_hash_class **))&br_sha256_init, + (void (*)(const br_hash_class **, + const void *, size_t))&br_sha256_update, + (void (*)(const br_hash_class *const *, void *))&br_sha256_out, + (uint64_t (*)(const br_hash_class *const *, void *))&br_sha256_state, + (void (*)(const br_hash_class **, const void *, uint64_t)) + &br_sha256_set_state +}; diff --git a/enclave/sgxsd_enclave/cmockery.c b/enclave/sgxsd_enclave/cmockery.c new file mode 100644 index 0000000..d28b742 --- /dev/null +++ b/enclave/sgxsd_enclave/cmockery.c @@ -0,0 +1,1773 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#ifdef HAVE_MALLOC_H +#include +#endif +#include +#ifndef _WIN32 +#include +#endif // !_WIN32 +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif // _WIN32 +#include + +#ifdef _WIN32 +#define vsnprintf _vsnprintf +#endif // _WIN32 + +/* Backwards compatibility with headers shipped with Visual Studio 2005 and + * earlier. */ +#ifdef _WIN32 +WINBASEAPI BOOL WINAPI IsDebuggerPresent(VOID); +#endif // _WIN32 + +// Size of guard bytes around dynamically allocated blocks. +#define MALLOC_GUARD_SIZE 16 +// Pattern used to initialize guard blocks. +#define MALLOC_GUARD_PATTERN 0xEF +// Pattern used to initialize memory allocated with test_malloc(). +#define MALLOC_ALLOC_PATTERN 0xBA +#define MALLOC_FREE_PATTERN 0xCD +// Alignment of allocated blocks. NOTE: This must be base2. +#define MALLOC_ALIGNMENT sizeof(size_t) + +// Printf formatting for source code locations. +#define SOURCE_LOCATION_FORMAT "%s:%d" + +// Calculates the number of elements in an array. +#define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0])) + +// Declare and initialize the pointer member of ValuePointer variable name +// with ptr. +#define declare_initialize_value_pointer_pointer(name, ptr) \ + ValuePointer name ; \ + name.value = 0; \ + name.pointer = (void*)(ptr) + +// Declare and initialize the value member of ValuePointer variable name +// with val. +#define declare_initialize_value_pointer_value(name, val) \ + ValuePointer name ; \ + name.value = val + +// Cast a LargestIntegralType to pointer_type via a ValuePointer. +#define cast_largest_integral_type_to_pointer( \ + pointer_type, largest_integral_type) \ + ((pointer_type)((ValuePointer*)&(largest_integral_type))->pointer) + +// Used to cast LargetIntegralType to void* and vice versa. +typedef union ValuePointer { + LargestIntegralType value; + void *pointer; +} ValuePointer; + +// Doubly linked list node. +typedef struct ListNode { + const void *value; + int refcount; + struct ListNode *next; + struct ListNode *prev; +} ListNode; + +// Debug information for malloc(). +typedef struct MallocBlockInfo { + void* block; // Address of the block returned by malloc(). + size_t allocated_size; // Total size of the allocated block. + size_t size; // Request block size. + SourceLocation location; // Where the block was allocated. + ListNode node; // Node within list of all allocated blocks. +} MallocBlockInfo; + +// State of each test. +typedef struct TestState { + const ListNode *check_point; // Check point of the test if there's a + // setup function. + void *state; // State associated with the test. +} TestState; + +// Determines whether two values are the same. +typedef int (*EqualityFunction)(const void *left, const void *right); + +// Value of a symbol and the place it was declared. +typedef struct SymbolValue { + SourceLocation location; + LargestIntegralType value; +} SymbolValue; + +/* Contains a list of values for a symbol. + * NOTE: Each structure referenced by symbol_values_list_head must have a + * SourceLocation as its' first member. + */ +typedef struct SymbolMapValue { + const char *symbol_name; + ListNode symbol_values_list_head; +} SymbolMapValue; + +// Used by list_free() to deallocate values referenced by list nodes. +typedef void (*CleanupListValue)(const void *value, void *cleanup_value_data); + +// Structure used to check the range of integer types. +typedef struct CheckIntegerRange { + CheckParameterEvent event; + LargestIntegralType minimum; + LargestIntegralType maximum; +} CheckIntegerRange; + +// Structure used to check whether an integer value is in a set. +typedef struct CheckIntegerSet { + CheckParameterEvent event; + const LargestIntegralType *set; + size_t size_of_set; +} CheckIntegerSet; + +/* Used to check whether a parameter matches the area of memory referenced by + * this structure. */ +typedef struct CheckMemoryData { + CheckParameterEvent event; + const void *memory; + size_t size; +} CheckMemoryData; + +static ListNode* list_initialize(ListNode * const node); +static ListNode* list_add(ListNode * const head, ListNode *new_node); +static ListNode* list_add_value(ListNode * const head, const void *value, + const int count); +static ListNode* list_remove( + ListNode * const node, const CleanupListValue cleanup_value, + void * const cleanup_value_data); +static void list_remove_free( + ListNode * const node, const CleanupListValue cleanup_value, + void * const cleanup_value_data); +static int list_empty(const ListNode * const head); +static int list_find( + ListNode * const head, const void *value, + const EqualityFunction equal_func, ListNode **output); +static int list_first(ListNode * const head, ListNode **output); +static ListNode* list_free( + ListNode * const head, const CleanupListValue cleanup_value, + void * const cleanup_value_data); + +static void add_symbol_value( + ListNode * const symbol_map_head, const char * const symbol_names[], + const size_t number_of_symbol_names, const void* value, const int count); +static int get_symbol_value( + ListNode * const symbol_map_head, const char * const symbol_names[], + const size_t number_of_symbol_names, void **output); +static void free_value(const void *value, void *cleanup_value_data); +static void free_symbol_map_value( + const void *value, void *cleanup_value_data); +static void remove_always_return_values(ListNode * const map_head, + const size_t number_of_symbol_names); +static int check_for_leftover_values( + const ListNode * const map_head, const char * const error_message, + const size_t number_of_symbol_names); +// This must be called at the beginning of a test to initialize some data +// structures. +static void initialize_testing(const char *test_name); +// This must be called at the end of a test to free() allocated structures. +static void teardown_testing(const char *test_name); + + +// Keeps track of the calling context returned by setenv() so that the fail() +// method can jump out of a test. +static jmp_buf global_run_test_env; +static int global_running_test = 0; + +// Keeps track of the calling context returned by setenv() so that +// mock_assert() can optionally jump back to expect_assert_failure(). +jmp_buf global_expect_assert_env; +int global_expecting_assert = 0; + +// Keeps a map of the values that functions will have to return to provide +// mocked interfaces. +static ListNode global_function_result_map_head; +// Location of the last mock value returned was declared. +static SourceLocation global_last_mock_value_location; + +/* Keeps a map of the values that functions expect as parameters to their + * mocked interfaces. */ +static ListNode global_function_parameter_map_head; +// Location of last parameter value checked was declared. +static SourceLocation global_last_parameter_location; + +// List of all currently allocated blocks. +static ListNode global_allocated_blocks; + +#ifndef _WIN32 +// Signals caught by exception_handler(). +static const int exception_signals[] = { + SIGFPE, + SIGILL, + SIGSEGV, + SIGBUS, + SIGSYS, +}; + +// Default signal functions that should be restored after a test is complete. +typedef void (*SignalFunction)(int signal); +static SignalFunction default_signal_functions[ + ARRAY_LENGTH(exception_signals)]; + +#else // _WIN32 + +// The default exception filter. +static LPTOP_LEVEL_EXCEPTION_FILTER previous_exception_filter; + +// Fatal exceptions. +typedef struct ExceptionCodeInfo { + DWORD code; + const char* description; +} ExceptionCodeInfo; + +#define EXCEPTION_CODE_INFO(exception_code) {exception_code, #exception_code} + +static const ExceptionCodeInfo exception_codes[] = { + EXCEPTION_CODE_INFO(EXCEPTION_ACCESS_VIOLATION), + EXCEPTION_CODE_INFO(EXCEPTION_ARRAY_BOUNDS_EXCEEDED), + EXCEPTION_CODE_INFO(EXCEPTION_DATATYPE_MISALIGNMENT), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_DENORMAL_OPERAND), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_DIVIDE_BY_ZERO), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_INEXACT_RESULT), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_INVALID_OPERATION), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_OVERFLOW), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_STACK_CHECK), + EXCEPTION_CODE_INFO(EXCEPTION_FLT_UNDERFLOW), + EXCEPTION_CODE_INFO(EXCEPTION_GUARD_PAGE), + EXCEPTION_CODE_INFO(EXCEPTION_ILLEGAL_INSTRUCTION), + EXCEPTION_CODE_INFO(EXCEPTION_INT_DIVIDE_BY_ZERO), + EXCEPTION_CODE_INFO(EXCEPTION_INT_OVERFLOW), + EXCEPTION_CODE_INFO(EXCEPTION_INVALID_DISPOSITION), + EXCEPTION_CODE_INFO(EXCEPTION_INVALID_HANDLE), + EXCEPTION_CODE_INFO(EXCEPTION_IN_PAGE_ERROR), + EXCEPTION_CODE_INFO(EXCEPTION_NONCONTINUABLE_EXCEPTION), + EXCEPTION_CODE_INFO(EXCEPTION_PRIV_INSTRUCTION), + EXCEPTION_CODE_INFO(EXCEPTION_STACK_OVERFLOW), +}; +#endif // !_WIN32 + + +// Exit the currently executing test. +static void exit_test(const int quit_application) { + if (global_running_test) { + longjmp(global_run_test_env, 1); + } else if (quit_application) { + exit(-1); + } +} + + +// Initialize a SourceLocation structure. +static void initialize_source_location(SourceLocation * const location) { + assert_true(location); + location->file = NULL; + location->line = 0; +} + + +// Determine whether a source location is currently set. +static int source_location_is_set(const SourceLocation * const location) { + assert_true(location); + return location->file && location->line; +} + + +// Set a source location. +static void set_source_location( + SourceLocation * const location, const char * const file, + const int line) { + assert_true(location); + location->file = file; + location->line = line; +} + + +// Create function results and expected parameter lists. +void initialize_testing(const char *test_name) { + list_initialize(&global_function_result_map_head); + initialize_source_location(&global_last_mock_value_location); + list_initialize(&global_function_parameter_map_head); + initialize_source_location(&global_last_parameter_location); +} + + +void fail_if_leftover_values(const char *test_name) { + int error_occurred = 0; + remove_always_return_values(&global_function_result_map_head, 1); + if (check_for_leftover_values( + &global_function_result_map_head, + "%s() has remaining non-returned values.\n", 1)) { + error_occurred = 1; + } + + remove_always_return_values(&global_function_parameter_map_head, 2); + if (check_for_leftover_values( + &global_function_parameter_map_head, + "%s parameter still has values that haven't been checked.\n", 2)) { + error_occurred = 1; + } + if (error_occurred) { + exit_test(1); + } +} + + +void teardown_testing(const char *test_name) { + list_free(&global_function_result_map_head, free_symbol_map_value, + (void*)0); + initialize_source_location(&global_last_mock_value_location); + list_free(&global_function_parameter_map_head, free_symbol_map_value, + (void*)1); + initialize_source_location(&global_last_parameter_location); +} + +// Initialize a list node. +static ListNode* list_initialize(ListNode * const node) { + node->value = NULL; + node->next = node; + node->prev = node; + node->refcount = 1; + return node; +} + + +/* Adds a value at the tail of a given list. + * The node referencing the value is allocated from the heap. */ +static ListNode* list_add_value(ListNode * const head, const void *value, + const int refcount) { + ListNode * const new_node = (ListNode*)malloc(sizeof(ListNode)); + assert_true(head); + assert_true(value); + new_node->value = value; + new_node->refcount = refcount; + return list_add(head, new_node); +} + + +// Add new_node to the end of the list. +static ListNode* list_add(ListNode * const head, ListNode *new_node) { + assert_true(head); + assert_true(new_node); + new_node->next = head; + new_node->prev = head->prev; + head->prev->next = new_node; + head->prev = new_node; + return new_node; +} + + +// Remove a node from a list. +static ListNode* list_remove( + ListNode * const node, const CleanupListValue cleanup_value, + void * const cleanup_value_data) { + assert_true(node); + node->prev->next = node->next; + node->next->prev = node->prev; + if (cleanup_value) { + cleanup_value(node->value, cleanup_value_data); + } + return node; +} + + +/* Remove a list node from a list and free the node. */ +static void list_remove_free( + ListNode * const node, const CleanupListValue cleanup_value, + void * const cleanup_value_data) { + assert_true(node); + free(list_remove(node, cleanup_value, cleanup_value_data)); +} + + +/* Frees memory kept by a linked list + * The cleanup_value function is called for every "value" field of nodes in the + * list, except for the head. In addition to each list value, + * cleanup_value_data is passed to each call to cleanup_value. The head + * of the list is not deallocated. + */ +static ListNode* list_free( + ListNode * const head, const CleanupListValue cleanup_value, + void * const cleanup_value_data) { + assert_true(head); + while (!list_empty(head)) { + list_remove_free(head->next, cleanup_value, cleanup_value_data); + } + return head; +} + + +// Determine whether a list is empty. +static int list_empty(const ListNode * const head) { + assert_true(head); + return head->next == head; +} + + +/* Find a value in the list using the equal_func to compare each node with the + * value. + */ +static int list_find(ListNode * const head, const void *value, + const EqualityFunction equal_func, ListNode **output) { + ListNode *current; + assert_true(head); + for (current = head->next; current != head; current = current->next) { + if (equal_func(current->value, value)) { + *output = current; + return 1; + } + } + return 0; +} + +// Returns the first node of a list +static int list_first(ListNode * const head, ListNode **output) { + ListNode *target_node; + assert_true(head); + if (list_empty(head)) { + return 0; + } + target_node = head->next; + *output = target_node; + return 1; +} + + +// Deallocate a value referenced by a list. +static void free_value(const void *value, void *cleanup_value_data) { + assert_true(value); + free((void*)value); +} + + +// Releases memory associated to a symbol_map_value. +static void free_symbol_map_value(const void *value, + void *cleanup_value_data) { + SymbolMapValue * const map_value = (SymbolMapValue*)value; + const uintptr_t children = (uintptr_t)cleanup_value_data; + assert_true(value); + list_free(&map_value->symbol_values_list_head, + children ? free_symbol_map_value : free_value, + (void*)(children - 1)); + free(map_value); +} + + +/* Determine whether a symbol name referenced by a symbol_map_value + * matches the specified function name. */ +static int symbol_names_match(const void *map_value, const void *symbol) { + return !strcmp(((SymbolMapValue*)map_value)->symbol_name, + (const char*)symbol); +} + + +/* Adds a value to the queue of values associated with the given + * hierarchy of symbols. It's assumed value is allocated from the heap. + */ +static void add_symbol_value(ListNode * const symbol_map_head, + const char * const symbol_names[], + const size_t number_of_symbol_names, + const void* value, const int refcount) { + const char* symbol_name; + ListNode *target_node; + SymbolMapValue *target_map_value; + assert_true(symbol_map_head); + assert_true(symbol_names); + assert_true(number_of_symbol_names); + symbol_name = symbol_names[0]; + + if (!list_find(symbol_map_head, symbol_name, symbol_names_match, + &target_node)) { + SymbolMapValue * const new_symbol_map_value = + malloc(sizeof(*new_symbol_map_value)); + new_symbol_map_value->symbol_name = symbol_name; + list_initialize(&new_symbol_map_value->symbol_values_list_head); + target_node = list_add_value(symbol_map_head, new_symbol_map_value, + 1); + } + + target_map_value = (SymbolMapValue*)target_node->value; + if (number_of_symbol_names == 1) { + list_add_value(&target_map_value->symbol_values_list_head, + value, refcount); + } else { + add_symbol_value(&target_map_value->symbol_values_list_head, + &symbol_names[1], number_of_symbol_names - 1, value, + refcount); + } +} + + +/* Gets the next value associated with the given hierarchy of symbols. + * The value is returned as an output parameter with the function returning the + * node's old refcount value if a value is found, 0 otherwise. + * This means that a return value of 1 indicates the node was just removed from + * the list. + */ +static int get_symbol_value( + ListNode * const head, const char * const symbol_names[], + const size_t number_of_symbol_names, void **output) { + const char* symbol_name; + ListNode *target_node; + assert_true(head); + assert_true(symbol_names); + assert_true(number_of_symbol_names); + assert_true(output); + symbol_name = symbol_names[0]; + + if (list_find(head, symbol_name, symbol_names_match, &target_node)) { + SymbolMapValue *map_value; + ListNode *child_list; + int return_value = 0; + assert_true(target_node); + assert_true(target_node->value); + + map_value = (SymbolMapValue*)target_node->value; + child_list = &map_value->symbol_values_list_head; + + if (number_of_symbol_names == 1) { + ListNode *value_node = NULL; + return_value = list_first(child_list, &value_node); + assert_true(return_value); + *output = (void*) value_node->value; + return_value = value_node->refcount; + if (--value_node->refcount == 0) { + list_remove_free(value_node, NULL, NULL); + } + } else { + return_value = get_symbol_value( + child_list, &symbol_names[1], number_of_symbol_names - 1, + output); + } + if (list_empty(child_list)) { + list_remove_free(target_node, free_symbol_map_value, (void*)0); + } + return return_value; + } else { + print_error("No entries for symbol %s.\n", symbol_name); + } + return 0; +} + + +/* Traverse down a tree of symbol values and remove the first symbol value + * in each branch that has a refcount < -1 (i.e should always be returned + * and has been returned at least once). + */ +static void remove_always_return_values(ListNode * const map_head, + const size_t number_of_symbol_names) { + ListNode *current; + assert_true(map_head); + assert_true(number_of_symbol_names); + current = map_head->next; + while (current != map_head) { + SymbolMapValue * const value = (SymbolMapValue*)current->value; + ListNode * const next = current->next; + ListNode *child_list; + assert_true(value); + child_list = &value->symbol_values_list_head; + + if (!list_empty(child_list)) { + if (number_of_symbol_names == 1) { + ListNode * const child_node = child_list->next; + // If this item has been returned more than once, free it. + if (child_node->refcount < -1) { + list_remove_free(child_node, free_value, NULL); + } + } else { + remove_always_return_values(child_list, + number_of_symbol_names - 1); + } + } + + if (list_empty(child_list)) { + list_remove_free(current, free_value, NULL); + } + current = next; + } +} + +/* Checks if there are any leftover values set up by the test that were never + * retrieved through execution, and fail the test if that is the case. + */ +static int check_for_leftover_values( + const ListNode * const map_head, const char * const error_message, + const size_t number_of_symbol_names) { + const ListNode *current; + int symbols_with_leftover_values = 0; + assert_true(map_head); + assert_true(number_of_symbol_names); + + for (current = map_head->next; current != map_head; + current = current->next) { + const SymbolMapValue * const value = + (SymbolMapValue*)current->value; + const ListNode *child_list; + assert_true(value); + child_list = &value->symbol_values_list_head; + + if (!list_empty(child_list)) { + if (number_of_symbol_names == 1) { + const ListNode *child_node; + print_error(error_message, value->symbol_name); + print_error(" Remaining item(s) declared at...\n"); + + for (child_node = child_list->next; child_node != child_list; + child_node = child_node->next) { + const SourceLocation * const location = child_node->value; + print_error(" " SOURCE_LOCATION_FORMAT "\n", + location->file, location->line); + } + } else { + print_error("%s.", value->symbol_name); + check_for_leftover_values(child_list, error_message, + number_of_symbol_names - 1); + } + symbols_with_leftover_values ++; + } + } + return symbols_with_leftover_values; +} + + +// Get the next return value for the specified mock function. +LargestIntegralType _mock(const char * const function, const char* const file, + const int line) { + void *result; + const int rc = get_symbol_value(&global_function_result_map_head, + &function, 1, &result); + if (rc) { + SymbolValue * const symbol = (SymbolValue*)result; + const LargestIntegralType value = symbol->value; + global_last_mock_value_location = symbol->location; + if (rc == 1) { + free(symbol); + } + return value; + } else { + print_error("ERROR: " SOURCE_LOCATION_FORMAT " - Could not get value " + "to mock function %s\n", file, line, function); + if (source_location_is_set(&global_last_mock_value_location)) { + print_error("Previously returned mock value was declared at " + SOURCE_LOCATION_FORMAT "\n", + global_last_mock_value_location.file, + global_last_mock_value_location.line); + } else { + print_error("There were no previously returned mock values for " + "this test.\n"); + } + exit_test(1); + } + return 0; +} + + +// Add a return value for the specified mock function name. +void _will_return(const char * const function_name, const char * const file, + const int line, const LargestIntegralType value, + const int count) { + SymbolValue * const return_value = malloc(sizeof(*return_value)); + assert_true(count > 0 || count == -1); + return_value->value = value; + set_source_location(&return_value->location, file, line); + add_symbol_value(&global_function_result_map_head, &function_name, 1, + return_value, count); +} + + +/* Add a custom parameter checking function. If the event parameter is NULL + * the event structure is allocated internally by this function. If event + * parameter is provided it must be allocated on the heap and doesn't need to + * be deallocated by the caller. + */ +void _expect_check( + const char* const function, const char* const parameter, + const char* const file, const int line, + const CheckParameterValue check_function, + const LargestIntegralType check_data, + CheckParameterEvent * const event, const int count) { + CheckParameterEvent * const check = + event ? event : malloc(sizeof(*check)); + const char* symbols[] = {function, parameter}; + check->parameter_name = parameter; + check->check_value = check_function; + check->check_value_data = check_data; + set_source_location(&check->location, file, line); + add_symbol_value(&global_function_parameter_map_head, symbols, 2, check, + count); +} + + +/* Returns 1 if the specified values are equal. If the values are not equal + * an error is displayed and 0 is returned. */ +static int values_equal_display_error(const LargestIntegralType left, + const LargestIntegralType right) { + const int equal = left == right; + if (!equal) { + print_error(LargestIntegralTypePrintfFormat " != " + LargestIntegralTypePrintfFormat "\n", left, right); + } + return equal; +} + +/* Returns 1 if the specified values are not equal. If the values are equal + * an error is displayed and 0 is returned. */ +static int values_not_equal_display_error(const LargestIntegralType left, + const LargestIntegralType right) { + const int not_equal = left != right; + if (!not_equal) { + print_error(LargestIntegralTypePrintfFormat " == " + LargestIntegralTypePrintfFormat "\n", left, right); + } + return not_equal; +} + + +/* Determine whether value is contained within check_integer_set. + * If invert is 0 and the value is in the set 1 is returned, otherwise 0 is + * returned and an error is displayed. If invert is 1 and the value is not + * in the set 1 is returned, otherwise 0 is returned and an error is + * displayed. */ +static int value_in_set_display_error( + const LargestIntegralType value, + const CheckIntegerSet * const check_integer_set, const int invert) { + int succeeded = invert; + assert_true(check_integer_set); + { + const LargestIntegralType * const set = check_integer_set->set; + const size_t size_of_set = check_integer_set->size_of_set; + size_t i; + for (i = 0; i < size_of_set; i++) { + if (set[i] == value) { + // If invert = 0 and item is found, succeeded = 1. + // If invert = 1 and item is found, succeeded = 0. + succeeded = !succeeded; + break; + } + } + if (succeeded) { + return 1; + } + print_error("%d is %sin the set (", value, invert ? "" : "not "); + for (i = 0; i < size_of_set; i++) { + print_error("%d, ", set[i]); + } + print_error(")\n"); + } + return 0; +} + + +/* Determine whether a value is within the specified range. If the value is + * within the specified range 1 is returned. If the value isn't within the + * specified range an error is displayed and 0 is returned. */ +static int integer_in_range_display_error( + const LargestIntegralType value, const LargestIntegralType range_min, + const LargestIntegralType range_max) { + if (value >= range_min && value <= range_max) { + return 1; + } + print_error("%d is not within the range %d-%d\n", value, range_min, + range_max); + return 0; +} + + +/* Determine whether a value is within the specified range. If the value + * is not within the range 1 is returned. If the value is within the + * specified range an error is displayed and zero is returned. */ +static int integer_not_in_range_display_error( + const LargestIntegralType value, const LargestIntegralType range_min, + const LargestIntegralType range_max) { + if (value < range_min || value > range_max) { + return 1; + } + print_error("%d is within the range %d-%d\n", value, range_min, + range_max); + return 0; +} + + +/* Determine whether the specified strings are equal. If the strings are equal + * 1 is returned. If they're not equal an error is displayed and 0 is + * returned. */ +static int string_equal_display_error( + const char * const left, const char * const right) { + if (strcmp(left, right) == 0) { + return 1; + } + print_error("\"%s\" != \"%s\"\n", left, right); + return 0; +} + + +/* Determine whether the specified strings are equal. If the strings are not + * equal 1 is returned. If they're not equal an error is displayed and 0 is + * returned */ +static int string_not_equal_display_error( + const char * const left, const char * const right) { + if (strcmp(left, right) != 0) { + return 1; + } + print_error("\"%s\" == \"%s\"\n", left, right); + return 0; +} + + +/* Determine whether the specified areas of memory are equal. If they're equal + * 1 is returned otherwise an error is displayed and 0 is returned. */ +static int memory_equal_display_error(const char* const a, const char* const b, + const size_t size) { + int differences = 0; + size_t i; + for (i = 0; i < size; i++) { + const char l = a[i]; + const char r = b[i]; + if (l != r) { + print_error("difference at offset %d 0x%02x 0x%02x\n", i, l, r); + differences ++; + } + } + if (differences) { + print_error("%d bytes of 0x%08x and 0x%08x differ\n", differences, + a, b); + return 0; + } + return 1; +} + + +/* Determine whether the specified areas of memory are not equal. If they're + * not equal 1 is returned otherwise an error is displayed and 0 is + * returned. */ +static int memory_not_equal_display_error( + const char* const a, const char* const b, const size_t size) { + size_t same = 0; + size_t i; + for (i = 0; i < size; i++) { + const char l = a[i]; + const char r = b[i]; + if (l == r) { + same ++; + } + } + if (same == size) { + print_error("%d bytes of 0x%08x and 0x%08x the same\n", same, + a, b); + return 0; + } + return 1; +} + + +// CheckParameterValue callback to check whether a value is within a set. +static int check_in_set(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return value_in_set_display_error(value, + cast_largest_integral_type_to_pointer(CheckIntegerSet*, + check_value_data), 0); +} + + +// CheckParameterValue callback to check whether a value isn't within a set. +static int check_not_in_set(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return value_in_set_display_error(value, + cast_largest_integral_type_to_pointer(CheckIntegerSet*, + check_value_data), 1); +} + + +/* Create the callback data for check_in_set() or check_not_in_set() and + * register a check event. */ +static void expect_set( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType values[], const size_t number_of_values, + const CheckParameterValue check_function, const int count) { + CheckIntegerSet * const check_integer_set = + malloc(sizeof(*check_integer_set) + + (sizeof(values[0]) * number_of_values)); + LargestIntegralType * const set = (LargestIntegralType*)( + check_integer_set + 1); + declare_initialize_value_pointer_pointer(check_data, check_integer_set); + assert_true(values); + assert_true(number_of_values); + memcpy(set, values, number_of_values * sizeof(values[0])); + check_integer_set->set = set; + _expect_check( + function, parameter, file, line, check_function, + check_data.value, &check_integer_set->event, count); +} + + +// Add an event to check whether a value is in a set. +void _expect_in_set( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType values[], const size_t number_of_values, + const int count) { + expect_set(function, parameter, file, line, values, number_of_values, + check_in_set, count); +} + + +// Add an event to check whether a value isn't in a set. +void _expect_not_in_set( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType values[], const size_t number_of_values, + const int count) { + expect_set(function, parameter, file, line, values, number_of_values, + check_not_in_set, count); +} + + +// CheckParameterValue callback to check whether a value is within a range. +static int check_in_range(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + CheckIntegerRange * const check_integer_range = + cast_largest_integral_type_to_pointer(CheckIntegerRange*, + check_value_data); + assert_true(check_integer_range); + return integer_in_range_display_error(value, check_integer_range->minimum, + check_integer_range->maximum); +} + + +// CheckParameterValue callback to check whether a value is not within a range. +static int check_not_in_range(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + CheckIntegerRange * const check_integer_range = + cast_largest_integral_type_to_pointer(CheckIntegerRange*, + check_value_data); + assert_true(check_integer_range); + return integer_not_in_range_display_error( + value, check_integer_range->minimum, check_integer_range->maximum); +} + + +/* Create the callback data for check_in_range() or check_not_in_range() and + * register a check event. */ +static void expect_range( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType minimum, const LargestIntegralType maximum, + const CheckParameterValue check_function, const int count) { + CheckIntegerRange * const check_integer_range = + malloc(sizeof(*check_integer_range)); + declare_initialize_value_pointer_pointer(check_data, check_integer_range); + check_integer_range->minimum = minimum; + check_integer_range->maximum = maximum; + _expect_check(function, parameter, file, line, check_function, + check_data.value, &check_integer_range->event, count); +} + + +// Add an event to determine whether a parameter is within a range. +void _expect_in_range( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType minimum, const LargestIntegralType maximum, + const int count) { + expect_range(function, parameter, file, line, minimum, maximum, + check_in_range, count); +} + + +// Add an event to determine whether a parameter is not within a range. +void _expect_not_in_range( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType minimum, const LargestIntegralType maximum, + const int count) { + expect_range(function, parameter, file, line, minimum, maximum, + check_not_in_range, count); +} + + +/* CheckParameterValue callback to check whether a value is equal to an + * expected value. */ +static int check_value(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return values_equal_display_error(value, check_value_data); +} + + +// Add an event to check a parameter equals an expected value. +void _expect_value( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType value, const int count) { + _expect_check(function, parameter, file, line, check_value, value, NULL, + count); +} + + +/* CheckParameterValue callback to check whether a value is not equal to an + * expected value. */ +static int check_not_value(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return values_not_equal_display_error(value, check_value_data); +} + + +// Add an event to check a parameter is not equal to an expected value. +void _expect_not_value( + const char* const function, const char* const parameter, + const char* const file, const int line, + const LargestIntegralType value, const int count) { + _expect_check(function, parameter, file, line, check_not_value, value, + NULL, count); +} + + +// CheckParameterValue callback to check whether a parameter equals a string. +static int check_string(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return string_equal_display_error( + cast_largest_integral_type_to_pointer(char*, value), + cast_largest_integral_type_to_pointer(char*, check_value_data)); +} + + +// Add an event to check whether a parameter is equal to a string. +void _expect_string( + const char* const function, const char* const parameter, + const char* const file, const int line, const char* string, + const int count) { + declare_initialize_value_pointer_pointer(string_pointer, (char*)string); + _expect_check(function, parameter, file, line, check_string, + string_pointer.value, NULL, count); +} + + +/* CheckParameterValue callback to check whether a parameter is not equals to + * a string. */ +static int check_not_string(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return string_not_equal_display_error( + cast_largest_integral_type_to_pointer(char*, value), + cast_largest_integral_type_to_pointer(char*, check_value_data)); +} + + +// Add an event to check whether a parameter is not equal to a string. +void _expect_not_string( + const char* const function, const char* const parameter, + const char* const file, const int line, const char* string, + const int count) { + declare_initialize_value_pointer_pointer(string_pointer, (char*)string); + _expect_check(function, parameter, file, line, check_not_string, + string_pointer.value, NULL, count); +} + +/* CheckParameterValue callback to check whether a parameter equals an area of + * memory. */ +static int check_memory(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + CheckMemoryData * const check = cast_largest_integral_type_to_pointer( + CheckMemoryData*, check_value_data); + assert_true(check); + return memory_equal_display_error( + cast_largest_integral_type_to_pointer(void*, value), + check->memory, check->size); +} + + +/* Create the callback data for check_memory() or check_not_memory() and + * register a check event. */ +static void expect_memory_setup( + const char* const function, const char* const parameter, + const char* const file, const int line, + const void * const memory, const size_t size, + const CheckParameterValue check_function, const int count) { + CheckMemoryData * const check_data = malloc(sizeof(*check_data) + size); + void * const mem = (void*)(check_data + 1); + declare_initialize_value_pointer_pointer(check_data_pointer, check_data); + assert_true(memory); + assert_true(size); + memcpy(mem, memory, size); + check_data->memory = mem; + check_data->size = size; + _expect_check(function, parameter, file, line, check_function, + check_data_pointer.value, &check_data->event, count); +} + + +// Add an event to check whether a parameter matches an area of memory. +void _expect_memory( + const char* const function, const char* const parameter, + const char* const file, const int line, const void* const memory, + const size_t size, const int count) { + expect_memory_setup(function, parameter, file, line, memory, size, + check_memory, count); +} + + +/* CheckParameterValue callback to check whether a parameter is not equal to + * an area of memory. */ +static int check_not_memory(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + CheckMemoryData * const check = cast_largest_integral_type_to_pointer( + CheckMemoryData*, check_value_data); + assert_true(check); + return memory_not_equal_display_error( + cast_largest_integral_type_to_pointer(void*, value), check->memory, + check->size); +} + + +// Add an event to check whether a parameter doesn't match an area of memory. +void _expect_not_memory( + const char* const function, const char* const parameter, + const char* const file, const int line, const void* const memory, + const size_t size, const int count) { + expect_memory_setup(function, parameter, file, line, memory, size, + check_not_memory, count); +} + + +// CheckParameterValue callback that always returns 1. +static int check_any(const LargestIntegralType value, + const LargestIntegralType check_value_data) { + return 1; +} + + +// Add an event to allow any value for a parameter. +void _expect_any( + const char* const function, const char* const parameter, + const char* const file, const int line, const int count) { + _expect_check(function, parameter, file, line, check_any, 0, NULL, + count); +} + + +void _check_expected( + const char * const function_name, const char * const parameter_name, + const char* file, const int line, const LargestIntegralType value) { + void *result; + const char* symbols[] = {function_name, parameter_name}; + const int rc = get_symbol_value(&global_function_parameter_map_head, + symbols, 2, &result); + if (rc) { + CheckParameterEvent * const check = (CheckParameterEvent*)result; + int check_succeeded; + global_last_parameter_location = check->location; + check_succeeded = check->check_value(value, check->check_value_data); + if (rc == 1) { + free(check); + } + if (!check_succeeded) { + print_error("ERROR: Check of parameter %s, function %s failed\n" + "Expected parameter declared at " + SOURCE_LOCATION_FORMAT "\n", + parameter_name, function_name, + global_last_parameter_location.file, + global_last_parameter_location.line); + _fail(file, line); + } + } else { + print_error("ERROR: " SOURCE_LOCATION_FORMAT " - Could not get value " + "to check parameter %s of function %s\n", file, line, + parameter_name, function_name); + if (source_location_is_set(&global_last_parameter_location)) { + print_error("Previously declared parameter value was declared at " + SOURCE_LOCATION_FORMAT "\n", + global_last_parameter_location.file, + global_last_parameter_location.line); + } else { + print_error("There were no previously declared parameter values " + "for this test.\n"); + } + exit_test(1); + } +} + + +// Replacement for assert. +void mock_assert(const int result, const char* const expression, + const char* const file, const int line) { + if (!result) { + if (global_expecting_assert) { + longjmp(global_expect_assert_env, (int) (uintptr_t)expression); + } else { + print_error("ASSERT: %s\n", expression); + _fail(file, line); + } + } +} + + +void _assert_true(const LargestIntegralType result, + const char * const expression, + const char * const file, const int line) { + if (!result) { + print_error("%s\n", expression); + _fail(file, line); + } +} + +void _assert_int_equal( + const LargestIntegralType a, const LargestIntegralType b, + const char * const file, const int line) { + if (!values_equal_display_error(a, b)) { + _fail(file, line); + } +} + + +void _assert_int_not_equal( + const LargestIntegralType a, const LargestIntegralType b, + const char * const file, const int line) { + if (!values_not_equal_display_error(a, b)) { + _fail(file, line); + } +} + + +void _assert_string_equal(const char * const a, const char * const b, + const char * const file, const int line) { + if (!string_equal_display_error(a, b)) { + _fail(file, line); + } +} + + +void _assert_string_not_equal(const char * const a, const char * const b, + const char *file, const int line) { + if (!string_not_equal_display_error(a, b)) { + _fail(file, line); + } +} + + +void _assert_memory_equal(const void * const a, const void * const b, + const size_t size, const char* const file, + const int line) { + if (!memory_equal_display_error((const char*)a, (const char*)b, size)) { + _fail(file, line); + } +} + + +void _assert_memory_not_equal(const void * const a, const void * const b, + const size_t size, const char* const file, + const int line) { + if (!memory_not_equal_display_error((const char*)a, (const char*)b, + size)) { + _fail(file, line); + } +} + + +void _assert_in_range( + const LargestIntegralType value, const LargestIntegralType minimum, + const LargestIntegralType maximum, const char* const file, + const int line) { + if (!integer_in_range_display_error(value, minimum, maximum)) { + _fail(file, line); + } +} + +void _assert_not_in_range( + const LargestIntegralType value, const LargestIntegralType minimum, + const LargestIntegralType maximum, const char* const file, + const int line) { + if (!integer_not_in_range_display_error(value, minimum, maximum)) { + _fail(file, line); + } +} + +void _assert_in_set(const LargestIntegralType value, + const LargestIntegralType values[], + const size_t number_of_values, const char* const file, + const int line) { + CheckIntegerSet check_integer_set; + check_integer_set.set = values; + check_integer_set.size_of_set = number_of_values; + if (!value_in_set_display_error(value, &check_integer_set, 0)) { + _fail(file, line); + } +} + +void _assert_not_in_set(const LargestIntegralType value, + const LargestIntegralType values[], + const size_t number_of_values, const char* const file, + const int line) { + CheckIntegerSet check_integer_set; + check_integer_set.set = values; + check_integer_set.size_of_set = number_of_values; + if (!value_in_set_display_error(value, &check_integer_set, 1)) { + _fail(file, line); + } +} + + +// Get the list of allocated blocks. +static ListNode* get_allocated_blocks_list() { + // If it initialized, initialize the list of allocated blocks. + if (!global_allocated_blocks.value) { + list_initialize(&global_allocated_blocks); + global_allocated_blocks.value = (void*)1; + } + return &global_allocated_blocks; +} + +// Use the real malloc in this function. +#undef malloc +void* _test_malloc(const size_t size, const char* file, const int line) { + return _test_memalign(MALLOC_ALIGNMENT, size, file, line); +} +void* _test_memalign(const size_t alignment, const size_t size, const char* file, const int line) { + assert_true(__builtin_popcount(alignment) == 1); + char* ptr; + MallocBlockInfo *block_info; + ListNode * const block_list = get_allocated_blocks_list(); + const size_t allocate_size = size + (MALLOC_GUARD_SIZE * 2) + + sizeof(*block_info) + alignment; + assert_true(allocate_size > size); + char* const block = (char*)malloc(allocate_size); + assert_true(block); + + // Calculate the returned address. + ptr = (char*)(((size_t)block + MALLOC_GUARD_SIZE + sizeof(*block_info) + + alignment) & ~(alignment - 1)); + + // Initialize the guard blocks. + memset(ptr - MALLOC_GUARD_SIZE, MALLOC_GUARD_PATTERN, MALLOC_GUARD_SIZE); + memset(ptr + size, MALLOC_GUARD_PATTERN, MALLOC_GUARD_SIZE); + memset(ptr, MALLOC_ALLOC_PATTERN, size); + + block_info = (MallocBlockInfo*)(ptr - (MALLOC_GUARD_SIZE + + sizeof(*block_info))); + set_source_location(&block_info->location, file, line); + block_info->allocated_size = allocate_size; + block_info->size = size; + block_info->block = block; + block_info->node.value = block_info; + list_add(block_list, &block_info->node); + return ptr; +} +#define malloc test_malloc + + +void* _test_calloc(const size_t number_of_elements, const size_t size, + const char* file, const int line) { + void* const ptr = _test_malloc(number_of_elements * size, file, line); + if (ptr) { + memset(ptr, 0, number_of_elements * size); + } + return ptr; +} + + +// Use the real free in this function. +#undef free +void _test_free(void* const ptr, const char* file, const int line) { + unsigned int i; + char *block = (char*)ptr; + MallocBlockInfo *block_info; + _assert_true((uintptr_t)ptr, "ptr", file, line); + block_info = (MallocBlockInfo*)(block - (MALLOC_GUARD_SIZE + + sizeof(*block_info))); + // Check the guard blocks. + { + char *guards[2] = {block - MALLOC_GUARD_SIZE, + block + block_info->size}; + for (i = 0; i < ARRAY_LENGTH(guards); i++) { + unsigned int j; + char * const guard = guards[i]; + for (j = 0; j < MALLOC_GUARD_SIZE; j++) { + const char diff = guard[j] - MALLOC_GUARD_PATTERN; + if (diff) { + print_error( + "Guard block of 0x%08x size=%d allocated by " + SOURCE_LOCATION_FORMAT " at 0x%08x is corrupt\n", + (size_t)ptr, block_info->size, + block_info->location.file, block_info->location.line, + (size_t)&guard[j]); + _fail(file, line); + } + } + } + } + list_remove(&block_info->node, NULL, NULL); + + block = block_info->block; + memset(block, MALLOC_FREE_PATTERN, block_info->allocated_size); + free(block); +} +#define free test_free + + +// Crudely checkpoint the current heap state. +static const ListNode* check_point_allocated_blocks() { + return get_allocated_blocks_list()->prev; +} + + +/* Display the blocks allocated after the specified check point. This + * function returns the number of blocks displayed. */ +static int display_allocated_blocks(const ListNode * const check_point) { + const ListNode * const head = get_allocated_blocks_list(); + const ListNode *node; + int allocated_blocks = 0; + assert_true(check_point); + assert_true(check_point->next); + + for (node = check_point->next; node != head; node = node->next) { + const MallocBlockInfo * const block_info = node->value; + assert_true(block_info); + + if (!allocated_blocks) { + print_error("Blocks allocated...\n"); + } + print_error(" 0x%08x : " SOURCE_LOCATION_FORMAT "\n", + block_info->block, block_info->location.file, + block_info->location.line); + allocated_blocks ++; + } + return allocated_blocks; +} + + +// Free all blocks allocated after the specified check point. +static void free_allocated_blocks(const ListNode * const check_point) { + const ListNode * const head = get_allocated_blocks_list(); + const ListNode *node; + assert_true(check_point); + + node = check_point->next; + assert_true(node); + + while (node != head) { + MallocBlockInfo * const block_info = (MallocBlockInfo*)node->value; + node = node->next; + free((char*)block_info + sizeof(*block_info) + MALLOC_GUARD_SIZE); + } +} + + +// Fail if any any blocks are allocated after the specified check point. +static void fail_if_blocks_allocated(const ListNode * const check_point, + const char * const test_name) { + const int allocated_blocks = display_allocated_blocks(check_point); + if (allocated_blocks) { + free_allocated_blocks(check_point); + print_error("ERROR: %s leaked %d block(s)\n", test_name, + allocated_blocks); + exit_test(1); + } +} + + +void _fail(const char * const file, const int line) { + print_error("ERROR: " SOURCE_LOCATION_FORMAT " Failure!\n", file, line); + exit_test(1); +} + + +#ifndef _WIN32 +static void exception_handler(int sig) { + print_error("%s\n", strsignal(sig)); + exit_test(1); +} + +#else // _WIN32 + +static LONG WINAPI exception_filter(EXCEPTION_POINTERS *exception_pointers) { + EXCEPTION_RECORD * const exception_record = + exception_pointers->ExceptionRecord; + const DWORD code = exception_record->ExceptionCode; + unsigned int i; + for (i = 0; i < ARRAY_LENGTH(exception_codes); i++) { + const ExceptionCodeInfo * const code_info = &exception_codes[i]; + if (code == code_info->code) { + static int shown_debug_message = 0; + fflush(stdout); + print_error("%s occurred at 0x%08x.\n", code_info->description, + exception_record->ExceptionAddress); + if (!shown_debug_message) { + print_error( + "\n" + "To debug in Visual Studio...\n" + "1. Select menu item File->Open Project\n" + "2. Change 'Files of type' to 'Executable Files'\n" + "3. Open this executable.\n" + "4. Select menu item Debug->Start\n" + "\n" + "Alternatively, set the environment variable \n" + "UNIT_TESTING_DEBUG to 1 and rebuild this executable, \n" + "then click 'Debug' in the popup dialog box.\n" + "\n"); + shown_debug_message = 1; + } + exit_test(0); + return EXCEPTION_EXECUTE_HANDLER; + } + } + return EXCEPTION_CONTINUE_SEARCH; +} +#endif // !_WIN32 + + +// Standard output and error print methods. +void vprint_message(const char* const format, va_list args) { + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), format, args); + puts(buffer); +#ifdef _WIN32 + OutputDebugString(buffer); +#endif // _WIN32 +} + + +void vprint_error(const char* const format, va_list args) { + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), format, args); + fputs(buffer, stderr); +#ifdef _WIN32 + OutputDebugString(buffer); +#endif // _WIN32 +} + + +void print_message(const char* const format, ...) { + va_list args; + va_start(args, format); + vprint_message(format, args); + va_end(args); +} + + +void print_error(const char* const format, ...) { + va_list args; + va_start(args, format); + vprint_error(format, args); + va_end(args); +} + + +int _run_test( + const char * const function_name, const UnitTestFunction Function, + void ** const state, const UnitTestFunctionType function_type, + const void* const heap_check_point) { + const ListNode * const check_point = heap_check_point ? + heap_check_point : check_point_allocated_blocks(); + void *current_state = NULL; + int rc = 1; + int handle_exceptions = 1; +#ifdef _WIN32 + handle_exceptions = !IsDebuggerPresent(); +#endif // _WIN32 +#if UNIT_TESTING_DEBUG + handle_exceptions = 0; +#endif // UNIT_TESTING_DEBUG + + if (handle_exceptions) { +#ifndef _WIN32 + unsigned int i; + for (i = 0; i < ARRAY_LENGTH(exception_signals); i++) { + default_signal_functions[i] = signal( + exception_signals[i], exception_handler); + } +#else // _WIN32 + previous_exception_filter = SetUnhandledExceptionFilter( + exception_filter); +#endif // !_WIN32 + } + + if (function_type == UNIT_TEST_FUNCTION_TYPE_TEST) { + print_message("%s: Starting test\n", function_name); + } + initialize_testing(function_name); + global_running_test = 1; + if (setjmp(global_run_test_env) == 0) { + Function(state ? state : ¤t_state); + fail_if_leftover_values(function_name); + + /* If this is a setup function then ignore any allocated blocks + * only ensure they're deallocated on tear down. */ + if (function_type != UNIT_TEST_FUNCTION_TYPE_SETUP) { + fail_if_blocks_allocated(check_point, function_name); + } + + global_running_test = 0; + + if (function_type == UNIT_TEST_FUNCTION_TYPE_TEST) { + print_message("%s: Test completed successfully.\n", function_name); + } + rc = 0; + } else { + global_running_test = 0; + print_message("%s: Test failed.\n", function_name); + } + teardown_testing(function_name); + + if (handle_exceptions) { +#ifndef _WIN32 + unsigned int i; + for (i = 0; i < ARRAY_LENGTH(exception_signals); i++) { + signal(exception_signals[i], default_signal_functions[i]); + } +#else // _WIN32 + if (previous_exception_filter) { + SetUnhandledExceptionFilter(previous_exception_filter); + previous_exception_filter = NULL; + } +#endif // !_WIN32 + } + + return rc; +} + + +int _run_tests(const UnitTest * const tests, const size_t number_of_tests) { + // Whether to execute the next test. + int run_next_test = 1; + // Whether the previous test failed. + int previous_test_failed = 0; + // Check point of the heap state. + const ListNode * const check_point = check_point_allocated_blocks(); + // Current test being executed. + size_t current_test = 0; + // Number of tests executed. + size_t tests_executed = 0; + // Number of failed tests. + size_t total_failed = 0; + // Number of setup functions. + size_t setups = 0; + // Number of teardown functions. + size_t teardowns = 0; + /* A stack of test states. A state is pushed on the stack + * when a test setup occurs and popped on tear down. */ + TestState* test_states = malloc(number_of_tests * sizeof(*test_states)); + size_t number_of_test_states = 0; + // Names of the tests that failed. + const char** failed_names = malloc(number_of_tests * + sizeof(*failed_names)); + void **current_state = NULL; + // Make sure LargestIntegralType is at least the size of a pointer. + assert_true(sizeof(LargestIntegralType) >= sizeof(void*)); + + while (current_test < number_of_tests) { + const ListNode *test_check_point = NULL; + TestState *current_TestState; + const UnitTest * const test = &tests[current_test++]; + if (!test->function) { + continue; + } + + switch (test->function_type) { + case UNIT_TEST_FUNCTION_TYPE_TEST: + run_next_test = 1; + break; + case UNIT_TEST_FUNCTION_TYPE_SETUP: { + // Checkpoint the heap before the setup. + current_TestState = &test_states[number_of_test_states++]; + current_TestState->check_point = check_point_allocated_blocks(); + test_check_point = current_TestState->check_point; + current_state = ¤t_TestState->state; + *current_state = NULL; + run_next_test = 1; + setups ++; + break; + } + case UNIT_TEST_FUNCTION_TYPE_TEARDOWN: + // Check the heap based on the last setup checkpoint. + assert_true(number_of_test_states); + current_TestState = &test_states[--number_of_test_states]; + test_check_point = current_TestState->check_point; + current_state = ¤t_TestState->state; + teardowns ++; + break; + default: + print_error("Invalid unit test function type %d\n", + test->function_type); + exit_test(1); + break; + } + + if (run_next_test) { + int failed = _run_test(test->name, test->function, current_state, + test->function_type, test_check_point); + if (failed) { + failed_names[total_failed] = test->name; + } + + switch (test->function_type) { + case UNIT_TEST_FUNCTION_TYPE_TEST: + previous_test_failed = failed; + total_failed += failed; + tests_executed ++; + break; + + case UNIT_TEST_FUNCTION_TYPE_SETUP: + if (failed) { + total_failed ++; + tests_executed ++; + // Skip forward until the next test or setup function. + run_next_test = 0; + } + previous_test_failed = 0; + break; + + case UNIT_TEST_FUNCTION_TYPE_TEARDOWN: + // If this test failed. + if (failed && !previous_test_failed) { + total_failed ++; + } + break; + default: + assert_false("BUG: shouldn't be here!"); + break; + } + } + } + + if (total_failed) { + size_t i; + print_error("%d out of %d tests failed!\n", total_failed, + tests_executed); + for (i = 0; i < total_failed; i++) { + print_error(" %s\n", failed_names[i]); + } + } else { + print_message("All %d tests passed\n", tests_executed); + } + + if (number_of_test_states) { + print_error("Mismatched number of setup %d and teardown %d " + "functions\n", setups, teardowns); + total_failed = -1; + } + + free(test_states); + free((void*)failed_names); + + fail_if_blocks_allocated(check_point, "run_tests"); + return (int)total_failed; +} diff --git a/enclave/sgxsd_enclave/curve25519-donna-c64.c b/enclave/sgxsd_enclave/curve25519-donna-c64.c new file mode 100644 index 0000000..9ebd8a1 --- /dev/null +++ b/enclave/sgxsd_enclave/curve25519-donna-c64.c @@ -0,0 +1,449 @@ +/* Copyright 2008, Google Inc. + * All rights reserved. + * + * Code released into the public domain. + * + * curve25519-donna: Curve25519 elliptic curve, public key function + * + * http://code.google.com/p/curve25519-donna/ + * + * Adam Langley + * + * Derived from public domain C code by Daniel J. Bernstein + * + * More information about curve25519 can be found here + * http://cr.yp.to/ecdh.html + * + * djb's sample implementation of curve25519 is written in a special assembly + * language called qhasm and uses the floating point registers. + * + * This is, almost, a clean room reimplementation from the curve25519 paper. It + * uses many of the tricks described therein. Only the crecip function is taken + * from the sample implementation. + */ + +#include +#include + +typedef uint8_t u8; +typedef uint64_t limb; +typedef limb felem[5]; +// This is a special gcc mode for 128-bit integers. It's implemented on 64-bit +// platforms only as far as I know. +typedef unsigned uint128_t __attribute__((mode(TI))); + +#undef force_inline +#define force_inline __attribute__((always_inline)) + +/* Sum two numbers: output += in */ +static inline void force_inline +fsum(limb *output, const limb *in) { + output[0] += in[0]; + output[1] += in[1]; + output[2] += in[2]; + output[3] += in[3]; + output[4] += in[4]; +} + +/* Find the difference of two numbers: output = in - output + * (note the order of the arguments!) + * + * Assumes that out[i] < 2**52 + * On return, out[i] < 2**55 + */ +static inline void force_inline +fdifference_backwards(felem out, const felem in) { + /* 152 is 19 << 3 */ + static const limb two54m152 = (((limb)1) << 54) - 152; + static const limb two54m8 = (((limb)1) << 54) - 8; + + out[0] = in[0] + two54m152 - out[0]; + out[1] = in[1] + two54m8 - out[1]; + out[2] = in[2] + two54m8 - out[2]; + out[3] = in[3] + two54m8 - out[3]; + out[4] = in[4] + two54m8 - out[4]; +} + +/* Multiply a number by a scalar: output = in * scalar */ +static inline void force_inline +fscalar_product(felem output, const felem in, const limb scalar) { + uint128_t a; + + a = ((uint128_t) in[0]) * scalar; + output[0] = ((limb)a) & 0x7ffffffffffff; + + a = ((uint128_t) in[1]) * scalar + ((limb) (a >> 51)); + output[1] = ((limb)a) & 0x7ffffffffffff; + + a = ((uint128_t) in[2]) * scalar + ((limb) (a >> 51)); + output[2] = ((limb)a) & 0x7ffffffffffff; + + a = ((uint128_t) in[3]) * scalar + ((limb) (a >> 51)); + output[3] = ((limb)a) & 0x7ffffffffffff; + + a = ((uint128_t) in[4]) * scalar + ((limb) (a >> 51)); + output[4] = ((limb)a) & 0x7ffffffffffff; + + output[0] += (a >> 51) * 19; +} + +/* Multiply two numbers: output = in2 * in + * + * output must be distinct to both inputs. The inputs are reduced coefficient + * form, the output is not. + * + * Assumes that in[i] < 2**55 and likewise for in2. + * On return, output[i] < 2**52 + */ +static inline void force_inline +fmul(felem output, const felem in2, const felem in) { + uint128_t t[5]; + limb r0,r1,r2,r3,r4,s0,s1,s2,s3,s4,c; + + r0 = in[0]; + r1 = in[1]; + r2 = in[2]; + r3 = in[3]; + r4 = in[4]; + + s0 = in2[0]; + s1 = in2[1]; + s2 = in2[2]; + s3 = in2[3]; + s4 = in2[4]; + + t[0] = ((uint128_t) r0) * s0; + t[1] = ((uint128_t) r0) * s1 + ((uint128_t) r1) * s0; + t[2] = ((uint128_t) r0) * s2 + ((uint128_t) r2) * s0 + ((uint128_t) r1) * s1; + t[3] = ((uint128_t) r0) * s3 + ((uint128_t) r3) * s0 + ((uint128_t) r1) * s2 + ((uint128_t) r2) * s1; + t[4] = ((uint128_t) r0) * s4 + ((uint128_t) r4) * s0 + ((uint128_t) r3) * s1 + ((uint128_t) r1) * s3 + ((uint128_t) r2) * s2; + + r4 *= 19; + r1 *= 19; + r2 *= 19; + r3 *= 19; + + t[0] += ((uint128_t) r4) * s1 + ((uint128_t) r1) * s4 + ((uint128_t) r2) * s3 + ((uint128_t) r3) * s2; + t[1] += ((uint128_t) r4) * s2 + ((uint128_t) r2) * s4 + ((uint128_t) r3) * s3; + t[2] += ((uint128_t) r4) * s3 + ((uint128_t) r3) * s4; + t[3] += ((uint128_t) r4) * s4; + + r0 = (limb)t[0] & 0x7ffffffffffff; c = (limb)(t[0] >> 51); + t[1] += c; r1 = (limb)t[1] & 0x7ffffffffffff; c = (limb)(t[1] >> 51); + t[2] += c; r2 = (limb)t[2] & 0x7ffffffffffff; c = (limb)(t[2] >> 51); + t[3] += c; r3 = (limb)t[3] & 0x7ffffffffffff; c = (limb)(t[3] >> 51); + t[4] += c; r4 = (limb)t[4] & 0x7ffffffffffff; c = (limb)(t[4] >> 51); + r0 += c * 19; c = r0 >> 51; r0 = r0 & 0x7ffffffffffff; + r1 += c; c = r1 >> 51; r1 = r1 & 0x7ffffffffffff; + r2 += c; + + output[0] = r0; + output[1] = r1; + output[2] = r2; + output[3] = r3; + output[4] = r4; +} + +static inline void force_inline +fsquare_times(felem output, const felem in, limb count) { + uint128_t t[5]; + limb r0,r1,r2,r3,r4,c; + limb d0,d1,d2,d4,d419; + + r0 = in[0]; + r1 = in[1]; + r2 = in[2]; + r3 = in[3]; + r4 = in[4]; + + do { + d0 = r0 * 2; + d1 = r1 * 2; + d2 = r2 * 2 * 19; + d419 = r4 * 19; + d4 = d419 * 2; + + t[0] = ((uint128_t) r0) * r0 + ((uint128_t) d4) * r1 + (((uint128_t) d2) * (r3 )); + t[1] = ((uint128_t) d0) * r1 + ((uint128_t) d4) * r2 + (((uint128_t) r3) * (r3 * 19)); + t[2] = ((uint128_t) d0) * r2 + ((uint128_t) r1) * r1 + (((uint128_t) d4) * (r3 )); + t[3] = ((uint128_t) d0) * r3 + ((uint128_t) d1) * r2 + (((uint128_t) r4) * (d419 )); + t[4] = ((uint128_t) d0) * r4 + ((uint128_t) d1) * r3 + (((uint128_t) r2) * (r2 )); + + r0 = (limb)t[0] & 0x7ffffffffffff; c = (limb)(t[0] >> 51); + t[1] += c; r1 = (limb)t[1] & 0x7ffffffffffff; c = (limb)(t[1] >> 51); + t[2] += c; r2 = (limb)t[2] & 0x7ffffffffffff; c = (limb)(t[2] >> 51); + t[3] += c; r3 = (limb)t[3] & 0x7ffffffffffff; c = (limb)(t[3] >> 51); + t[4] += c; r4 = (limb)t[4] & 0x7ffffffffffff; c = (limb)(t[4] >> 51); + r0 += c * 19; c = r0 >> 51; r0 = r0 & 0x7ffffffffffff; + r1 += c; c = r1 >> 51; r1 = r1 & 0x7ffffffffffff; + r2 += c; + } while(--count); + + output[0] = r0; + output[1] = r1; + output[2] = r2; + output[3] = r3; + output[4] = r4; +} + +/* Load a little-endian 64-bit number */ +static limb +load_limb(const u8 *in) { + return + ((limb)in[0]) | + (((limb)in[1]) << 8) | + (((limb)in[2]) << 16) | + (((limb)in[3]) << 24) | + (((limb)in[4]) << 32) | + (((limb)in[5]) << 40) | + (((limb)in[6]) << 48) | + (((limb)in[7]) << 56); +} + +static void +store_limb(u8 *out, limb in) { + out[0] = in & 0xff; + out[1] = (in >> 8) & 0xff; + out[2] = (in >> 16) & 0xff; + out[3] = (in >> 24) & 0xff; + out[4] = (in >> 32) & 0xff; + out[5] = (in >> 40) & 0xff; + out[6] = (in >> 48) & 0xff; + out[7] = (in >> 56) & 0xff; +} + +/* Take a little-endian, 32-byte number and expand it into polynomial form */ +static void +fexpand(limb *output, const u8 *in) { + output[0] = load_limb(in) & 0x7ffffffffffff; + output[1] = (load_limb(in+6) >> 3) & 0x7ffffffffffff; + output[2] = (load_limb(in+12) >> 6) & 0x7ffffffffffff; + output[3] = (load_limb(in+19) >> 1) & 0x7ffffffffffff; + output[4] = (load_limb(in+24) >> 12) & 0x7ffffffffffff; +} + +/* Take a fully reduced polynomial form number and contract it into a + * little-endian, 32-byte array + */ +static void +fcontract(u8 *output, const felem input) { + uint128_t t[5]; + + t[0] = input[0]; + t[1] = input[1]; + t[2] = input[2]; + t[3] = input[3]; + t[4] = input[4]; + + t[1] += t[0] >> 51; t[0] &= 0x7ffffffffffff; + t[2] += t[1] >> 51; t[1] &= 0x7ffffffffffff; + t[3] += t[2] >> 51; t[2] &= 0x7ffffffffffff; + t[4] += t[3] >> 51; t[3] &= 0x7ffffffffffff; + t[0] += 19 * (t[4] >> 51); t[4] &= 0x7ffffffffffff; + + t[1] += t[0] >> 51; t[0] &= 0x7ffffffffffff; + t[2] += t[1] >> 51; t[1] &= 0x7ffffffffffff; + t[3] += t[2] >> 51; t[2] &= 0x7ffffffffffff; + t[4] += t[3] >> 51; t[3] &= 0x7ffffffffffff; + t[0] += 19 * (t[4] >> 51); t[4] &= 0x7ffffffffffff; + + /* now t is between 0 and 2^255-1, properly carried. */ + /* case 1: between 0 and 2^255-20. case 2: between 2^255-19 and 2^255-1. */ + + t[0] += 19; + + t[1] += t[0] >> 51; t[0] &= 0x7ffffffffffff; + t[2] += t[1] >> 51; t[1] &= 0x7ffffffffffff; + t[3] += t[2] >> 51; t[2] &= 0x7ffffffffffff; + t[4] += t[3] >> 51; t[3] &= 0x7ffffffffffff; + t[0] += 19 * (t[4] >> 51); t[4] &= 0x7ffffffffffff; + + /* now between 19 and 2^255-1 in both cases, and offset by 19. */ + + t[0] += 0x8000000000000 - 19; + t[1] += 0x8000000000000 - 1; + t[2] += 0x8000000000000 - 1; + t[3] += 0x8000000000000 - 1; + t[4] += 0x8000000000000 - 1; + + /* now between 2^255 and 2^256-20, and offset by 2^255. */ + + t[1] += t[0] >> 51; t[0] &= 0x7ffffffffffff; + t[2] += t[1] >> 51; t[1] &= 0x7ffffffffffff; + t[3] += t[2] >> 51; t[2] &= 0x7ffffffffffff; + t[4] += t[3] >> 51; t[3] &= 0x7ffffffffffff; + t[4] &= 0x7ffffffffffff; + + store_limb(output, t[0] | (t[1] << 51)); + store_limb(output+8, (t[1] >> 13) | (t[2] << 38)); + store_limb(output+16, (t[2] >> 26) | (t[3] << 25)); + store_limb(output+24, (t[3] >> 39) | (t[4] << 12)); +} + +/* Input: Q, Q', Q-Q' + * Output: 2Q, Q+Q' + * + * x2 z3: long form + * x3 z3: long form + * x z: short form, destroyed + * xprime zprime: short form, destroyed + * qmqp: short form, preserved + */ +static void +fmonty(limb *x2, limb *z2, /* output 2Q */ + limb *x3, limb *z3, /* output Q + Q' */ + limb *x, limb *z, /* input Q */ + limb *xprime, limb *zprime, /* input Q' */ + const limb *qmqp /* input Q - Q' */) { + limb origx[5], origxprime[5], zzz[5], xx[5], zz[5], xxprime[5], + zzprime[5], zzzprime[5]; + + memcpy(origx, x, 5 * sizeof(limb)); + fsum(x, z); + fdifference_backwards(z, origx); // does x - z + + memcpy(origxprime, xprime, sizeof(limb) * 5); + fsum(xprime, zprime); + fdifference_backwards(zprime, origxprime); + fmul(xxprime, xprime, z); + fmul(zzprime, x, zprime); + memcpy(origxprime, xxprime, sizeof(limb) * 5); + fsum(xxprime, zzprime); + fdifference_backwards(zzprime, origxprime); + fsquare_times(x3, xxprime, 1); + fsquare_times(zzzprime, zzprime, 1); + fmul(z3, zzzprime, qmqp); + + fsquare_times(xx, x, 1); + fsquare_times(zz, z, 1); + fmul(x2, xx, zz); + fdifference_backwards(zz, xx); // does zz = xx - zz + fscalar_product(zzz, zz, 121665); + fsum(zzz, xx); + fmul(z2, zz, zzz); +} + +// ----------------------------------------------------------------------------- +// Maybe swap the contents of two limb arrays (@a and @b), each @len elements +// long. Perform the swap iff @swap is non-zero. +// +// This function performs the swap without leaking any side-channel +// information. +// ----------------------------------------------------------------------------- +static void +swap_conditional(limb a[5], limb b[5], limb iswap) { + unsigned i; + const limb swap = -iswap; + + for (i = 0; i < 5; ++i) { + const limb x = swap & (a[i] ^ b[i]); + a[i] ^= x; + b[i] ^= x; + } +} + +/* Calculates nQ where Q is the x-coordinate of a point on the curve + * + * resultx/resultz: the x coordinate of the resulting curve point (short form) + * n: a little endian, 32-byte number + * q: a point of the curve (short form) + */ +static void +cmult(limb *resultx, limb *resultz, const u8 *n, const limb *q) { + limb a[5] = {0}, b[5] = {1}, c[5] = {1}, d[5] = {0}; + limb *nqpqx = a, *nqpqz = b, *nqx = c, *nqz = d, *t; + limb e[5] = {0}, f[5] = {1}, g[5] = {0}, h[5] = {1}; + limb *nqpqx2 = e, *nqpqz2 = f, *nqx2 = g, *nqz2 = h; + + unsigned i, j; + + memcpy(nqpqx, q, sizeof(limb) * 5); + + for (i = 0; i < 32; ++i) { + u8 byte = n[31 - i]; + for (j = 0; j < 8; ++j) { + const limb bit = byte >> 7; + + swap_conditional(nqx, nqpqx, bit); + swap_conditional(nqz, nqpqz, bit); + fmonty(nqx2, nqz2, + nqpqx2, nqpqz2, + nqx, nqz, + nqpqx, nqpqz, + q); + swap_conditional(nqx2, nqpqx2, bit); + swap_conditional(nqz2, nqpqz2, bit); + + t = nqx; + nqx = nqx2; + nqx2 = t; + t = nqz; + nqz = nqz2; + nqz2 = t; + t = nqpqx; + nqpqx = nqpqx2; + nqpqx2 = t; + t = nqpqz; + nqpqz = nqpqz2; + nqpqz2 = t; + + byte <<= 1; + } + } + + memcpy(resultx, nqx, sizeof(limb) * 5); + memcpy(resultz, nqz, sizeof(limb) * 5); +} + + +// ----------------------------------------------------------------------------- +// Shamelessly copied from djb's code, tightened a little +// ----------------------------------------------------------------------------- +static void +crecip(felem out, const felem z) { + felem a,t0,b,c; + + /* 2 */ fsquare_times(a, z, 1); // a = 2 + /* 8 */ fsquare_times(t0, a, 2); + /* 9 */ fmul(b, t0, z); // b = 9 + /* 11 */ fmul(a, b, a); // a = 11 + /* 22 */ fsquare_times(t0, a, 1); + /* 2^5 - 2^0 = 31 */ fmul(b, t0, b); + /* 2^10 - 2^5 */ fsquare_times(t0, b, 5); + /* 2^10 - 2^0 */ fmul(b, t0, b); + /* 2^20 - 2^10 */ fsquare_times(t0, b, 10); + /* 2^20 - 2^0 */ fmul(c, t0, b); + /* 2^40 - 2^20 */ fsquare_times(t0, c, 20); + /* 2^40 - 2^0 */ fmul(t0, t0, c); + /* 2^50 - 2^10 */ fsquare_times(t0, t0, 10); + /* 2^50 - 2^0 */ fmul(b, t0, b); + /* 2^100 - 2^50 */ fsquare_times(t0, b, 50); + /* 2^100 - 2^0 */ fmul(c, t0, b); + /* 2^200 - 2^100 */ fsquare_times(t0, c, 100); + /* 2^200 - 2^0 */ fmul(t0, t0, c); + /* 2^250 - 2^50 */ fsquare_times(t0, t0, 50); + /* 2^250 - 2^0 */ fmul(t0, t0, b); + /* 2^255 - 2^5 */ fsquare_times(t0, t0, 5); + /* 2^255 - 21 */ fmul(out, t0, a); +} + +int curve25519_donna(u8 *, const u8 *, const u8 *); + +int +curve25519_donna(u8 *mypublic, const u8 *secret, const u8 *basepoint) { + limb bp[5], x[5], z[5], zmone[5]; + uint8_t e[32]; + int i; + + for (i = 0;i < 32;++i) e[i] = secret[i]; + e[0] &= 248; + e[31] &= 127; + e[31] |= 64; + + fexpand(bp, basepoint); + cmult(x, z, e, bp); + crecip(zmone, z); + fmul(z, x, zmone); + fcontract(mypublic, z); + return 0; +} diff --git a/enclave/sgxsd_enclave/sgx-tcrypto-stub.c b/enclave/sgxsd_enclave/sgx-tcrypto-stub.c new file mode 100644 index 0000000..8561fbe --- /dev/null +++ b/enclave/sgxsd_enclave/sgx-tcrypto-stub.c @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + /** + * @author Jeff Griffin + */ + +#include + +#include "sgx_error.h" + +sgx_status_t sgx_init_crypto_lib(uint64_t cpu_feature_indicator, uint32_t *cpuinfo_table) { + return SGX_SUCCESS; +} diff --git a/enclave/sgxsd_enclave/sgxsd-enclave-test.c b/enclave/sgxsd_enclave/sgxsd-enclave-test.c new file mode 100644 index 0000000..16840cc --- /dev/null +++ b/enclave/sgxsd_enclave/sgxsd-enclave-test.c @@ -0,0 +1,891 @@ +/* + * Copyright (C) 2017 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + /** + * @author Jeff Griffin + */ +#include +#include +#include +#include +#include +#include +#include + +#include "sgx_trts.h" +#include "sgx_tcrypto.h" +#include "sgx_spinlock.h" +#include "sgx_quote.h" + +#include "bearssl.h" +#include "sgxsd-enclave.h" +#include "sgxsd.h" + +#include "cmockery.h" + +sgx_status_t sgxsd_enclave_node_init(const sgxsd_node_init_args_t* p_args); +sgx_status_t sgxsd_enclave_get_next_report(sgx_target_info_t qe_target_info, sgx_report_t *p_report); +sgx_status_t sgxsd_enclave_set_current_quote(); +sgx_status_t sgxsd_enclave_negotiate_request(const sgxsd_request_negotiation_request_t *p_request, sgxsd_request_negotiation_response_t *p_response); +sgx_status_t sgxsd_enclave_server_start(const sgxsd_server_init_args_t* p_args, sgxsd_server_state_handle_t state_handle); +sgx_status_t sgxsd_enclave_server_call(const sgxsd_server_handle_call_args_t* p_args, const sgxsd_msg_header_t* msg_header, const uint8_t* msg_data, size_t msg_size, sgxsd_msg_tag_t msg_tag, sgxsd_server_state_handle_t state_handle); +sgx_status_t sgxsd_enclave_server_stop(const sgxsd_server_terminate_args_t* p_args, sgxsd_server_state_handle_t state_handle); + +extern void *g_sgxsd_enclave_pending_requests; + +sgx_status_t sgxsd_ocall_reply(sgx_status_t* retval, const sgxsd_msg_header_t* reply_header, const uint8_t* reply_data, size_t reply_data_size, sgxsd_msg_tag_t msg_tag); + +void test_read_rand(void *dst, size_t size); +void expect_sgx_read_rand(sgx_status_t res, unsigned char **p_rand, size_t expected_length_in_bytes); +void expect_sgxsd_enclave_server_init(sgx_status_t res, void *expected_args, size_t expected_args_size); +void expect_sgxsd_enclave_server_handle_call(sgx_status_t res, void *expected_args, size_t expected_args_size, + sgxsd_msg_buf_t expected_msg, sgxsd_msg_from_t expected_from); +void expect_sgxsd_enclave_server_terminate(sgx_status_t res, void *expected_args, size_t expected_args_size); +void expect_sgxsd_aes_gcm_encrypt(sgx_status_t res, + const sgxsd_aes_gcm_key_t *expected_p_key, + void *expected_p_src, uint32_t expected_src_len, bool capture_src, + void **pp_expected_dst, + const sgxsd_aes_gcm_iv_t *expected_p_iv, + const void *expected_p_aad, uint32_t expected_aad_len, + sgxsd_aes_gcm_mac_t **pp_expected_out_mac); +void expect_sgxsd_aes_gcm_decrypt(sgx_status_t res, + const sgxsd_aes_gcm_key_t *expected_p_key, + const void *expected_p_src, uint32_t expected_src_len, + void **pp_expected_dst, + const sgxsd_aes_gcm_iv_t *expected_p_iv, + const void *expected_p_aad, uint32_t expected_aad_len, + const sgxsd_aes_gcm_mac_t *expected_p_in_mac); +void expect_sgxsd_ocall_reply(sgx_status_t ocall_res, sgx_status_t res, + const void *p_expected_dst, size_t expected_reply_data_size, + const void *expected_iv, size_t expected_iv_len, + const sgxsd_aes_gcm_mac_t *p_expected_out_mac, + uint64_t expected_tag); +void expect_sgxsd_ocall_noreply(sgx_status_t ocall_res, sgx_status_t res, + uint64_t expected_tag); + +// +// globals +// + +long int test_drand48_seed; +struct drand48_data test_drand48_data; + +sgxsd_node_init_args_t *test_node_init_args; + +sgxsd_request_negotiation_request_t *p_test_request_negotiation_request; +sgx_target_info_t test_qe_target_info; +sgx_report_t *p_test_report; + +sgxsd_server_state_handle_t valid_server_handle = 0; +sgxsd_server_state_handle_t invalid_server_handle = UINT64_MAX; +void *test_args; +size_t test_args_size; +sgxsd_msg_buf_t test_msg_buf; + +sgxsd_msg_header_t test_msg_header; + +sgxsd_msg_buf_t null_msg_buf = { .data = NULL, .size = 0 }; +sgxsd_msg_buf_t empty_msg_buf = { .data = NULL, .size = 0 }; +sgxsd_msg_from_t valid_msg_from; + +sgxsd_aes_gcm_iv_t *test_zero_iv; + +static void setup_tests(void **state) { + print_message("using seed: 0x%08lx\n", test_drand48_seed); + srand48_r(test_drand48_seed, &test_drand48_data); + + test_node_init_args = test_malloc(sizeof(*test_node_init_args)); + *test_node_init_args = (sgxsd_node_init_args_t) { + .pending_requests_table_order = 0, + }; + + empty_msg_buf.data = malloc(0); + + long int test_args_size_rand; + lrand48_r(&test_drand48_data, &test_args_size_rand); + test_args_size = 1 + (test_args_size_rand & 0xFFFF); + print_message("using test args of size %zu\n", test_args_size); + test_args = test_malloc(test_args_size); + test_read_rand(test_args, test_args_size); + + long int test_msg_buf_size_rand; + lrand48_r(&test_drand48_data, &test_msg_buf_size_rand); + test_msg_buf.size = 1 + (test_msg_buf_size_rand & 0xFFFF); + print_message("using test msg of size %zu\n", test_msg_buf.size); + test_msg_buf.data = test_malloc(test_msg_buf.size); + test_read_rand(test_msg_buf.data, test_msg_buf.size); + + p_test_request_negotiation_request = test_malloc(sizeof(*p_test_request_negotiation_request)); + test_read_rand(p_test_request_negotiation_request, sizeof(*p_test_request_negotiation_request)); + test_read_rand(&test_qe_target_info, sizeof(test_qe_target_info)); + p_test_report = test_malloc(sizeof(*p_test_report)); + test_read_rand(p_test_report, sizeof(*p_test_report)); + + test_read_rand(&test_msg_header.iv, sizeof(test_msg_header.iv)); + test_read_rand(&test_msg_header.mac, sizeof(test_msg_header.mac)); + + test_read_rand(&valid_msg_from, sizeof(valid_msg_from)); + valid_msg_from.valid = true; + + test_zero_iv = test_malloc(sizeof(*test_zero_iv)); + memset(test_zero_iv, 0, sizeof(*test_zero_iv)); +} +static void teardown_tests(void **state) { + test_free(test_zero_iv); + test_free(test_node_init_args); + test_free(test_args); + test_free(test_msg_buf.data); + test_free(empty_msg_buf.data); + test_free(p_test_request_negotiation_request); + test_free(p_test_report); +} + +static void teardown_node_tests(void **state) { + // allocated by node_init + test_free(g_sgxsd_enclave_pending_requests); +} + +static void test_noop(void **state) { +} + +// +// node init tests +// + +static void test_sgxsd_node_init_rand_error(void **state) { + will_return(sgx_is_outside_enclave, 1); + will_return(sgx_is_outside_enclave, 1); + expect_sgx_read_rand(SGX_ERROR_UNEXPECTED, NULL, sizeof((sgxsd_curve25519_private_key_t*){0}->x)); + assert_int_equal(SGX_ERROR_UNEXPECTED, sgxsd_enclave_node_init(test_node_init_args)); + + will_return(sgx_is_outside_enclave, 1); + will_return(sgx_is_outside_enclave, 1); + expect_sgx_read_rand(SGX_SUCCESS, NULL, sizeof((sgxsd_curve25519_private_key_t*){0}->x)); + expect_sgx_read_rand(SGX_ERROR_UNEXPECTED, NULL, sizeof((sgxsd_curve25519_private_key_t*){0}->x)); + assert_int_equal(SGX_ERROR_UNEXPECTED, sgxsd_enclave_node_init(test_node_init_args)); + + will_return(sgx_is_outside_enclave, 1); + will_return(sgx_is_outside_enclave, 1); + expect_sgx_read_rand(SGX_SUCCESS, NULL, sizeof((sgxsd_curve25519_private_key_t*){0}->x)); + expect_sgx_read_rand(SGX_SUCCESS, NULL, sizeof((sgxsd_curve25519_private_key_t*){0}->x)); + expect_sgx_read_rand(SGX_ERROR_UNEXPECTED, NULL, sizeof(sgxsd_aes_gcm_key_t)); + assert_int_equal(SGX_ERROR_UNEXPECTED, sgxsd_enclave_node_init(test_node_init_args)); +} +static void test_sgxsd_node_init_null_args(void **state) { + will_return(sgx_is_outside_enclave, 1); + will_return(sgx_is_outside_enclave, 1); + assert_int_equal(SGX_ERROR_INVALID_PARAMETER, sgxsd_enclave_node_init(NULL)); +} +static void test_sgxsd_node_init_pending_requests_table_order_too_large(void **state) { + will_return(sgx_is_outside_enclave, 1); + will_return(sgx_is_outside_enclave, 1); + assert_int_equal(SGX_ERROR_INVALID_PARAMETER, sgxsd_enclave_node_init + (&(sgxsd_node_init_args_t) { .pending_requests_table_order = 64 })); + + will_return(sgx_is_outside_enclave, 1); + will_return(sgx_is_outside_enclave, 1); + assert_int_equal(SGX_ERROR_INVALID_PARAMETER, sgxsd_enclave_node_init + (&(sgxsd_node_init_args_t) { .pending_requests_table_order = UINT8_MAX })); +} +static void test_sgxsd_node_init(void **state) { + will_return(sgx_is_outside_enclave, 1); + will_return(sgx_is_outside_enclave, 1); + expect_sgx_read_rand(SGX_SUCCESS, NULL, sizeof((sgxsd_curve25519_private_key_t*){0}->x)); + expect_sgx_read_rand(SGX_SUCCESS, NULL, sizeof((sgxsd_curve25519_private_key_t*){0}->x)); + expect_sgx_read_rand(SGX_SUCCESS, NULL, sizeof(sgxsd_aes_gcm_key_t)); + assert_int_equal(SGX_SUCCESS, sgxsd_enclave_node_init(test_node_init_args)); +} +static void test_sgxsd_node_init_already_initialized(void **state) { + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_node_init(test_node_init_args)); +} + +// +// get_next_report tests +// + +static void test_sgxsd_get_next_report_node_uninitialized(void **state) { + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_get_next_report + (test_qe_target_info, p_test_report)); +} +static void test_sgxsd_get_next_report_null_report(void **state) { + assert_int_equal(SGX_ERROR_INVALID_PARAMETER, sgxsd_enclave_get_next_report + (test_qe_target_info, NULL)); +} + +// +// set_current_quote tests +// + +static void test_sgxsd_set_current_quote_node_uninitialized(void **state) { + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_set_current_quote()); +} + +// +// negotiate_request tests +// + +static void test_sgxsd_negotiate_request_node_uninitialized(void **state) { + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_negotiate_request + (p_test_request_negotiation_request, &(sgxsd_request_negotiation_response_t) {0})); +} +static void test_sgxsd_negotiate_request_null_request(void **state) { + assert_int_equal(SGX_ERROR_INVALID_PARAMETER, sgxsd_enclave_negotiate_request + (NULL, &(sgxsd_request_negotiation_response_t) {0})); +} +static void test_sgxsd_negotiate_request_null_response(void **state) { + assert_int_equal(SGX_ERROR_INVALID_PARAMETER, sgxsd_enclave_negotiate_request + (p_test_request_negotiation_request, NULL)); +} +static void test_sgxsd_negotiate_request_generate_keypair_rand_error(void **state) { + expect_sgx_read_rand(SGX_ERROR_UNEXPECTED, NULL, sizeof((sgxsd_curve25519_private_key_t*){0}->x)); + assert_int_equal(SGX_ERROR_UNEXPECTED, sgxsd_enclave_negotiate_request + (p_test_request_negotiation_request, &(sgxsd_request_negotiation_response_t) {0})); +} + +static void test_sgxsd_negotiate_request(uint8_t *expected_pending_request_id, sgxsd_pending_request_id_t *p_pending_request_id) { + expect_sgx_read_rand(SGX_SUCCESS, NULL, sizeof((sgxsd_curve25519_private_key_t*){0}->x)); + uint8_t *expected_p_iv_data; + expect_sgx_read_rand(SGX_SUCCESS, &expected_p_iv_data, sizeof(sgxsd_aes_gcm_iv_t)); + sgxsd_aes_gcm_iv_t *expected_p_iv = (sgxsd_aes_gcm_iv_t *) expected_p_iv_data; + + expect_sgxsd_aes_gcm_encrypt(SGX_SUCCESS, NULL, + expected_pending_request_id, sizeof(p_pending_request_id->data), true, NULL, + expected_p_iv, NULL, 0, NULL); + + void *p_expected_dst; + sgxsd_aes_gcm_mac_t *p_expected_out_mac; + expect_sgxsd_aes_gcm_encrypt(SGX_SUCCESS, NULL, + p_pending_request_id, sizeof(*p_pending_request_id), true, &p_expected_dst, + test_zero_iv, NULL, 0, &p_expected_out_mac); + assert_int_equal(SGX_SUCCESS, sgxsd_enclave_negotiate_request + (p_test_request_negotiation_request, &(sgxsd_request_negotiation_response_t) {0})); +} + +// +// server start tests +// + +static void test_sgxsd_server_start_node_uninitialized(void **state) { + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_server_start(NULL, valid_server_handle)); +} +static void test_sgxsd_server_start_invalid_handle(void **state) { + assert_int_equal(SGX_ERROR_INVALID_PARAMETER, sgxsd_enclave_server_start(NULL, invalid_server_handle)); +} +static void test_sgxsd_server_start_already_started(void **state) { + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_server_start(NULL, valid_server_handle)); +} +static void test_sgxsd_server_start_init_error(void **state) { + expect_sgxsd_enclave_server_init(SGX_ERROR_UNEXPECTED, test_args, test_args_size); + assert_int_equal(SGX_ERROR_UNEXPECTED, sgxsd_enclave_server_start(test_args, valid_server_handle)); +} +static void test_sgxsd_server_start_valid(void **state) { + expect_sgxsd_enclave_server_init(SGX_SUCCESS, test_args, test_args_size); + assert_int_equal(SGX_SUCCESS, sgxsd_enclave_server_start(test_args, valid_server_handle)); +} + +// +// server call tests +// + +static void test_sgxsd_server_call_node_uninitialized(void **state) { + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_server_call + (test_args, &test_msg_header, test_msg_buf.data, test_msg_buf.size, valid_msg_from.tag, + valid_server_handle)); +} +static void test_sgxsd_server_call_invalid_request_id(void **state) { + memset(&test_msg_header.pending_request_id, 0, sizeof(test_msg_header.pending_request_id)); + expect_sgxsd_aes_gcm_decrypt(SGX_ERROR_MAC_MISMATCH, NULL, + &test_msg_header.pending_request_id.data, sizeof(test_msg_header.pending_request_id.data), + NULL, + &test_msg_header.pending_request_id.iv, + NULL, 0, + &test_msg_header.pending_request_id.mac); + assert_int_equal(SGX_ERROR_MAC_MISMATCH, sgxsd_enclave_server_call + (test_args, &test_msg_header, test_msg_buf.data, test_msg_buf.size, valid_msg_from.tag, + valid_server_handle)); +} +static void test_sgxsd_server_call_invalid_handle(void **state) { + test_sgxsd_negotiate_request(NULL, &test_msg_header.pending_request_id); + assert_int_equal(SGX_ERROR_INVALID_PARAMETER, sgxsd_enclave_server_call + (test_args, &test_msg_header, test_msg_buf.data, test_msg_buf.size, valid_msg_from.tag, + invalid_server_handle)); +} +static void test_sgxsd_server_call_not_started(void **state) { + test_sgxsd_negotiate_request(NULL, &test_msg_header.pending_request_id); + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_server_call + (test_args, &test_msg_header, test_msg_buf.data, test_msg_buf.size, valid_msg_from.tag, + valid_server_handle)); +} +static void test_sgxsd_server_call(sgx_status_t res, sgx_status_t decrypt_msg_res, + sgx_status_t handle_call_res, sgxsd_msg_buf_t msg) { + uint8_t expected_pending_request_id[sizeof(test_msg_header.pending_request_id.data)]; + test_sgxsd_negotiate_request(&expected_pending_request_id[0], &test_msg_header.pending_request_id); + + void *p_expected_pending_request_id_data; + expect_sgxsd_aes_gcm_decrypt(SGX_SUCCESS, NULL, + &test_msg_header.pending_request_id.data, sizeof(test_msg_header.pending_request_id.data), + &p_expected_pending_request_id_data, + &test_msg_header.pending_request_id.iv, + NULL, 0, + &test_msg_header.pending_request_id.mac); + memcpy(p_expected_pending_request_id_data, &expected_pending_request_id, sizeof(expected_pending_request_id)); + + void *p_expected_decrypted_msg_buf_data; + expect_sgxsd_aes_gcm_decrypt(decrypt_msg_res, NULL, + msg.data, msg.size, + &p_expected_decrypted_msg_buf_data, + &test_msg_header.iv, + &test_msg_header.pending_request_id, sizeof(test_msg_header.pending_request_id), + &test_msg_header.mac); + sgxsd_msg_buf_t expected_decrypted_msg_buf = { .data = p_expected_decrypted_msg_buf_data, .size = msg.size }; + if (decrypt_msg_res == SGX_SUCCESS) { + expect_sgxsd_enclave_server_handle_call(handle_call_res, test_args, test_args_size, + expected_decrypted_msg_buf, valid_msg_from); + } + assert_int_equal(res, sgxsd_enclave_server_call + (test_args, &test_msg_header, msg.data, msg.size, valid_msg_from.tag, valid_server_handle)); +} +static void test_sgxsd_server_call_decrypt_msg_error(void **state) { + test_sgxsd_server_call(SGX_ERROR_UNEXPECTED, SGX_ERROR_UNEXPECTED, SGX_SUCCESS, test_msg_buf); +} +static void test_sgxsd_server_call_handler_error(void **state) { + test_sgxsd_server_call(SGX_ERROR_UNEXPECTED, SGX_SUCCESS, SGX_ERROR_UNEXPECTED, test_msg_buf); +} +static void test_sgxsd_server_call_empty(void **state) { + test_sgxsd_negotiate_request(NULL, &test_msg_header.pending_request_id); + assert_int_equal(SGX_ERROR_INVALID_PARAMETER, sgxsd_enclave_server_call + (test_args, &test_msg_header, NULL, 0, valid_msg_from.tag, + valid_server_handle)); +} +static void test_sgxsd_server_call_invalid_null_data(void **state) { + // SGX Edger8r-generated interfaces pass through NULL data pointers along with non-zero data size + test_sgxsd_negotiate_request(NULL, &test_msg_header.pending_request_id); + assert_int_equal(SGX_ERROR_INVALID_PARAMETER, sgxsd_enclave_server_call + (test_args, &test_msg_header, NULL, 1, valid_msg_from.tag, + valid_server_handle)); +} +static void test_sgxsd_server_call_valid(void **state) { + test_sgxsd_server_call(SGX_SUCCESS, SGX_SUCCESS, SGX_SUCCESS, test_msg_buf); +} +static void test_sgxsd_server_call_replay(void **state) { + test_sgxsd_server_call(SGX_SUCCESS, SGX_SUCCESS, SGX_SUCCESS, test_msg_buf); + expect_sgxsd_aes_gcm_decrypt(SGX_SUCCESS, NULL, + &test_msg_header.pending_request_id.data, sizeof(test_msg_header.pending_request_id.data), + NULL, + &test_msg_header.pending_request_id.iv, + NULL, 0, + &test_msg_header.pending_request_id.mac); + assert_int_equal(SGXSD_ERROR_PENDING_REQUEST_NOT_FOUND, sgxsd_enclave_server_call + (test_args, &test_msg_header, test_msg_buf.data, test_msg_buf.size, valid_msg_from.tag, valid_server_handle)); +} + +// +// server stop tests +// + +static void test_sgxsd_server_stop_node_uninitialized(void **state) { + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_server_stop(NULL, valid_server_handle)); +} +static void test_sgxsd_server_stop_invalid_handle(void **state) { + assert_int_equal(SGX_ERROR_INVALID_PARAMETER, sgxsd_enclave_server_start(NULL, invalid_server_handle)); +} +static void test_sgxsd_server_stop_already_stopped(void **state) { + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_server_stop(NULL, valid_server_handle)); +} +static void test_sgxsd_server_stop_terminate_error(void **state) { + expect_sgxsd_enclave_server_terminate(SGX_ERROR_UNEXPECTED, test_args, test_args_size); + assert_int_equal(SGX_ERROR_UNEXPECTED, sgxsd_enclave_server_stop(test_args, valid_server_handle)); +} +static void test_sgxsd_server_stop_valid(void **state) { + expect_sgxsd_enclave_server_terminate(SGX_SUCCESS, test_args, test_args_size); + assert_int_equal(SGX_SUCCESS, sgxsd_enclave_server_stop(test_args, valid_server_handle)); +} + +// +// reply tests +// + +static void test_sgxsd_server_reply(sgx_status_t res, + sgx_status_t encrypt_res, sgx_status_t reply_ocall_res, sgx_status_t reply_res, + sgxsd_msg_buf_t reply_buf, sgxsd_msg_from_t *p_msg_from) { + sgxsd_msg_from_t msg_from = valid_msg_from; + if (p_msg_from == NULL) { + p_msg_from = &msg_from; + } + + uint8_t *expected_iv_data; + expect_sgx_read_rand(SGX_SUCCESS, &expected_iv_data, sizeof(((sgxsd_aes_gcm_iv_t *) 0)->data)); + sgxsd_aes_gcm_iv_t *expected_iv = (sgxsd_aes_gcm_iv_t *) expected_iv_data; + expected_iv->data[0] |= 1; + void *p_expected_dst; + sgxsd_aes_gcm_mac_t *p_expected_out_mac; + expect_sgxsd_aes_gcm_encrypt(encrypt_res, NULL, + reply_buf.data, reply_buf.size, false, &p_expected_dst, + expected_iv, NULL, 0, &p_expected_out_mac); + if (encrypt_res == SGX_SUCCESS) { + expect_sgxsd_ocall_reply(reply_ocall_res, reply_res, p_expected_dst, reply_buf.size, + expected_iv, sizeof(expected_iv->data), + p_expected_out_mac, + p_msg_from->tag.tag); + } + + assert_int_equal(res, sgxsd_enclave_server_reply(reply_buf, p_msg_from)); +} + +static void test_sgxsd_server_noreply(sgx_status_t res, sgx_status_t reply_ocall_res, sgx_status_t reply_res, + sgxsd_msg_from_t *p_msg_from) { + sgxsd_msg_from_t msg_from = valid_msg_from; + if (p_msg_from == NULL) { + p_msg_from = &msg_from; + } + + expect_sgxsd_ocall_noreply(reply_ocall_res, reply_res, p_msg_from->tag.tag); + assert_int_equal(res, sgxsd_enclave_server_noreply(p_msg_from)); +} + +static void test_sgxsd_server_reply_invalid_buf(void **state) { + sgxsd_msg_from_t msg_from = valid_msg_from; + assert_int_equal(SGX_ERROR_INVALID_PARAMETER, sgxsd_enclave_server_reply + ((sgxsd_msg_buf_t) { .data = NULL, .size = test_msg_buf.size }, &msg_from)); +} +static void test_sgxsd_server_reply_rand_error(void **state) { + sgxsd_msg_from_t msg_from = valid_msg_from; + expect_sgx_read_rand(SGX_ERROR_UNEXPECTED, NULL, sizeof(((sgxsd_aes_gcm_iv_t*)0)->data)); + assert_int_equal(SGX_ERROR_UNEXPECTED, sgxsd_enclave_server_reply(test_msg_buf, &msg_from)); +} +static void test_sgxsd_server_reply_encrypt_error(void **state) { + test_sgxsd_server_reply(SGX_ERROR_UNEXPECTED, SGX_ERROR_UNEXPECTED, SGX_SUCCESS, SGX_SUCCESS, test_msg_buf, NULL); +} +static void test_sgxsd_server_reply_ocall_error(void **state) { + test_sgxsd_server_reply(SGX_ERROR_UNEXPECTED, SGX_SUCCESS, SGX_ERROR_UNEXPECTED, SGX_SUCCESS, test_msg_buf, NULL); + test_sgxsd_server_reply(SGX_ERROR_UNEXPECTED, SGX_SUCCESS, SGX_SUCCESS, SGX_ERROR_UNEXPECTED, test_msg_buf, NULL); +} +static void test_sgxsd_server_reply_empty(void **state) { + test_sgxsd_server_reply(SGX_SUCCESS, SGX_SUCCESS, SGX_SUCCESS, SGX_SUCCESS, null_msg_buf, NULL); + test_sgxsd_server_reply(SGX_SUCCESS, SGX_SUCCESS, SGX_SUCCESS, SGX_SUCCESS, empty_msg_buf, NULL); +} +static void test_sgxsd_server_reply_valid(void **state) { + test_sgxsd_server_reply(SGX_SUCCESS, SGX_SUCCESS, SGX_SUCCESS, SGX_SUCCESS, test_msg_buf, NULL); +} +static void test_sgxsd_server_reply_twice(void **state) { + sgxsd_msg_from_t msg_from = valid_msg_from; + test_sgxsd_server_reply(SGX_SUCCESS, SGX_SUCCESS, SGX_SUCCESS, SGX_SUCCESS, test_msg_buf, &msg_from); + + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_server_reply(test_msg_buf, &msg_from)); + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_server_noreply(&msg_from)); +} + +static void test_sgxsd_server_noreply_valid(void **state) { + test_sgxsd_server_noreply(SGX_SUCCESS, SGX_SUCCESS, SGX_SUCCESS, NULL); +} + +static void test_sgxsd_server_noreply_twice(void **state) { + sgxsd_msg_from_t msg_from = valid_msg_from; + test_sgxsd_server_noreply(SGX_SUCCESS, SGX_SUCCESS, SGX_SUCCESS, &msg_from); + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_server_noreply(&msg_from)); + assert_int_equal(SGX_ERROR_INVALID_STATE, sgxsd_enclave_server_reply(test_msg_buf, &msg_from)); +} + +int main(int argc, char *argv[]) { + UnitTest tests[] = { + unit_test_setup(tests, setup_tests), + + // uninitialized node tests + unit_test(test_sgxsd_get_next_report_node_uninitialized), + unit_test(test_sgxsd_set_current_quote_node_uninitialized), + unit_test(test_sgxsd_negotiate_request_node_uninitialized), + unit_test(test_sgxsd_server_start_node_uninitialized), + unit_test(test_sgxsd_server_call_node_uninitialized), + unit_test(test_sgxsd_server_stop_node_uninitialized), + + // node init tests + unit_test(test_sgxsd_node_init_rand_error), + unit_test(test_sgxsd_node_init_null_args), + unit_test(test_sgxsd_node_init_pending_requests_table_order_too_large), + unit_test_setup(node_tests, test_sgxsd_node_init), + unit_test(test_sgxsd_node_init_already_initialized), + + // get_next_report tests + unit_test(test_sgxsd_get_next_report_null_report), + + // negotiate_request tests + unit_test(test_sgxsd_negotiate_request_null_request), + unit_test(test_sgxsd_negotiate_request_null_response), + unit_test(test_sgxsd_negotiate_request_generate_keypair_rand_error), + + // server start tests + unit_test(test_sgxsd_server_start_invalid_handle), + unit_test_setup_teardown(test_sgxsd_server_start_already_started, test_sgxsd_server_start_valid, test_sgxsd_server_stop_valid), + unit_test_setup_teardown(test_sgxsd_server_start_init_error, test_noop, test_sgxsd_server_stop_already_stopped), + unit_test_setup_teardown(test_sgxsd_server_start_valid, test_noop, test_sgxsd_server_stop_valid), + + // call tests + unit_test_setup_teardown(test_sgxsd_server_call_invalid_request_id, test_sgxsd_server_start_valid, test_sgxsd_server_stop_valid), + unit_test_setup_teardown(test_sgxsd_server_call_invalid_handle, test_sgxsd_server_start_valid, test_sgxsd_server_stop_valid), + unit_test(test_sgxsd_server_call_not_started), + unit_test_setup_teardown(test_sgxsd_server_call_empty, test_sgxsd_server_start_valid, test_sgxsd_server_stop_valid), + unit_test_setup_teardown(test_sgxsd_server_call_invalid_null_data, test_sgxsd_server_start_valid, test_sgxsd_server_stop_valid), + unit_test_setup_teardown(test_sgxsd_server_call_decrypt_msg_error, test_sgxsd_server_start_valid, test_sgxsd_server_stop_valid), + unit_test_setup_teardown(test_sgxsd_server_call_handler_error, test_sgxsd_server_start_valid, test_sgxsd_server_stop_valid), + unit_test_setup_teardown(test_sgxsd_server_call_valid, test_sgxsd_server_start_valid, test_sgxsd_server_stop_valid), + unit_test_setup_teardown(test_sgxsd_server_call_replay, test_sgxsd_server_start_valid, test_sgxsd_server_stop_valid), + + // server stop tests + unit_test(test_sgxsd_server_stop_invalid_handle), + unit_test(test_sgxsd_server_stop_already_stopped), + unit_test_setup_teardown(test_sgxsd_server_stop_terminate_error, test_sgxsd_server_start_valid, test_sgxsd_server_stop_already_stopped), + unit_test_setup_teardown(test_sgxsd_server_stop_valid, test_sgxsd_server_start_valid, test_sgxsd_server_stop_already_stopped), + + // reply tests + unit_test(test_sgxsd_server_reply_invalid_buf), + unit_test(test_sgxsd_server_reply_rand_error), + unit_test(test_sgxsd_server_reply_encrypt_error), + unit_test(test_sgxsd_server_reply_ocall_error), + unit_test(test_sgxsd_server_reply_empty), + unit_test(test_sgxsd_server_reply_valid), + unit_test(test_sgxsd_server_reply_twice), + unit_test(test_sgxsd_server_noreply_valid), + unit_test(test_sgxsd_server_noreply_twice), + unit_test_teardown(node_tests, teardown_node_tests), + unit_test_teardown(tests, teardown_tests), + }; + return run_tests(tests); +} + +// +// private utils +// + +void test_read_rand(void *dst, size_t size) { + size_t dst_idx = 0; + for (; dst_idx < size - 3; dst_idx += 4) { + long int rand; + mrand48_r(&test_drand48_data, &rand); + *((uint32_t *) (dst + dst_idx)) = rand; + } + for (; dst_idx < size; dst_idx++) { + long int rand; + mrand48_r(&test_drand48_data, &rand); + *((uint8_t *) (dst + dst_idx)) = rand; + } +} + +// +// mock stubs +// + +void expect_sgx_read_rand(sgx_status_t res, unsigned char **p_rand, size_t expected_length_in_bytes) { + expect_value(sgx_read_rand, length_in_bytes, expected_length_in_bytes); + unsigned char *res_rand = NULL; + if (res == SGX_SUCCESS) { + res_rand = test_malloc(expected_length_in_bytes); + test_read_rand(res_rand, expected_length_in_bytes); + } + if (p_rand != NULL) { + *p_rand = res_rand; + } + will_return(sgx_read_rand, res_rand); + will_return(sgx_read_rand, res); +} + +sgx_status_t sgx_read_rand(unsigned char *rand, size_t length_in_bytes) { + check_expected(length_in_bytes); + assert_int_not_equal(rand, NULL); + void *res_rand = (void *) mock(); + if (res_rand != NULL) { + memcpy(rand, res_rand, length_in_bytes); + test_free(res_rand); + } + return (sgx_status_t) mock(); +} + +void expect_sgxsd_aes_gcm_encrypt(sgx_status_t res, + const sgxsd_aes_gcm_key_t *expected_p_key, + void *expected_p_src, uint32_t expected_src_len, bool capture_src, + void **pp_expected_dst, + const sgxsd_aes_gcm_iv_t *expected_p_iv, + const void *expected_p_aad, uint32_t expected_aad_len, + sgxsd_aes_gcm_mac_t **pp_expected_out_mac) { + uint8_t *res_p_dst = NULL; + sgxsd_aes_gcm_mac_t *res_out_mac = NULL; + if (res == SGX_SUCCESS) { + if (expected_src_len != 0) { + res_p_dst = test_malloc(expected_src_len); + } + res_out_mac = test_malloc(sizeof(*res_out_mac)); + test_read_rand(res_out_mac->data, sizeof(res_out_mac->data)); + } + if (pp_expected_dst != NULL) { + *pp_expected_dst = res_p_dst; + } + if (pp_expected_out_mac != NULL) { + *pp_expected_out_mac = res_out_mac; + } + if (expected_p_key != NULL) { + expect_memory(sgxsd_aes_gcm_encrypt, p_key, expected_p_key, sizeof(expected_p_key->data)); + } else { + expect_not_value(sgxsd_aes_gcm_encrypt, p_key, NULL); + } + expect_value(sgxsd_aes_gcm_encrypt, src_len, expected_src_len); + if (capture_src) { + will_return(sgxsd_aes_gcm_encrypt, expected_p_src); + expect_not_value(sgxsd_aes_gcm_encrypt, p_src, NULL); + } else { + will_return(sgxsd_aes_gcm_encrypt, NULL); + if (expected_src_len != 0) { + assert_int_not_equal(expected_p_src, NULL); + expect_memory(sgxsd_aes_gcm_encrypt, p_src, expected_p_src, expected_src_len); + } else { + expect_any(sgxsd_aes_gcm_encrypt, p_src); + } + } + will_return(sgxsd_aes_gcm_encrypt, res_p_dst); + expect_memory(sgxsd_aes_gcm_encrypt, p_iv, expected_p_iv, sizeof(expected_p_iv->data)); + expect_value(sgxsd_aes_gcm_encrypt, aad_len, expected_aad_len); + if (expected_aad_len != 0) { + assert_int_not_equal(expected_p_aad, NULL); + expect_memory(sgxsd_aes_gcm_encrypt, p_aad, expected_p_aad, expected_aad_len); + } else { + expect_any(sgxsd_aes_gcm_encrypt, p_aad); + } + will_return(sgxsd_aes_gcm_encrypt, res_out_mac); + will_return(sgxsd_aes_gcm_encrypt, res); +} +sgx_status_t sgxsd_aes_gcm_encrypt(const sgxsd_aes_gcm_key_t *p_key, + const void *p_src, uint32_t src_len, void *p_dst, + const sgxsd_aes_gcm_iv_t *p_iv, + const void *p_aad, uint32_t aad_len, + sgxsd_aes_gcm_mac_t *p_out_mac) { + check_expected(p_key); + check_expected(src_len); + void *res_p_src = (void *) mock(); + if (res_p_src != NULL) { + assert_int_not_equal(p_src, NULL); + memcpy(res_p_src, p_src, src_len); + } + check_expected(p_src); + void *res_p_dst = (void *) mock(); + if (res_p_dst != NULL) { + assert_int_not_equal(p_dst, NULL); + assert_int_not_equal(p_src, NULL); + memcpy(p_dst, res_p_dst, src_len); + test_free(res_p_dst); + } + check_expected(p_iv); + check_expected(aad_len); + check_expected(p_aad); + assert_int_not_equal(p_out_mac, NULL); + sgxsd_aes_gcm_mac_t *res_p_out_mac = (sgxsd_aes_gcm_mac_t *) mock(); + if (res_p_out_mac != NULL) { + memcpy(p_out_mac, res_p_out_mac, sizeof(*p_out_mac)); + test_free(res_p_out_mac); + } + + return (sgx_status_t) mock(); +} + +void expect_sgxsd_aes_gcm_decrypt(sgx_status_t res, + const sgxsd_aes_gcm_key_t *expected_p_key, + const void *expected_p_src, uint32_t expected_src_len, + void **pp_expected_dst, + const sgxsd_aes_gcm_iv_t *expected_p_iv, + const void *expected_p_aad, uint32_t expected_aad_len, + const sgxsd_aes_gcm_mac_t *expected_p_in_mac) { + uint8_t *res_p_dst = NULL; + if (res == SGX_SUCCESS) { + if (expected_src_len != 0) { + res_p_dst = test_malloc(expected_src_len); + } + } + if (pp_expected_dst != NULL) { + *pp_expected_dst = res_p_dst; + } + if (expected_p_key != NULL) { + expect_memory(sgxsd_aes_gcm_decrypt, p_key, expected_p_key, sizeof(expected_p_key->data)); + } else { + expect_not_value(sgxsd_aes_gcm_decrypt, p_key, NULL); + } + expect_value(sgxsd_aes_gcm_decrypt, src_len, expected_src_len); + if (expected_src_len != 0) { + assert_int_not_equal(expected_p_src, NULL); + expect_memory(sgxsd_aes_gcm_decrypt, p_src, expected_p_src, expected_src_len); + } else { + expect_any(sgxsd_aes_gcm_decrypt, p_src); + } + will_return(sgxsd_aes_gcm_decrypt, res_p_dst); + expect_memory(sgxsd_aes_gcm_decrypt, p_iv, expected_p_iv, sizeof(expected_p_iv->data)); + expect_value(sgxsd_aes_gcm_decrypt, aad_len, expected_aad_len); + if (expected_aad_len != 0) { + assert_int_not_equal(expected_p_aad, NULL); + expect_memory(sgxsd_aes_gcm_decrypt, p_aad, expected_p_aad, expected_aad_len); + } else { + expect_any(sgxsd_aes_gcm_decrypt, p_aad); + } + expect_memory(sgxsd_aes_gcm_decrypt, p_in_mac, expected_p_in_mac, sizeof(expected_p_in_mac->data)); + will_return(sgxsd_aes_gcm_decrypt, res); +} +sgx_status_t sgxsd_aes_gcm_decrypt(const sgxsd_aes_gcm_key_t *p_key, + const void *p_src, uint32_t src_len, void *p_dst, + const sgxsd_aes_gcm_iv_t *p_iv, + const void *p_aad, uint32_t aad_len, + const sgxsd_aes_gcm_mac_t *p_in_mac) { + check_expected(p_key); + check_expected(src_len); + check_expected(p_src); + void *res_p_dst = (void *) mock(); + if (res_p_dst != NULL) { + assert_int_not_equal(p_dst, NULL); + assert_int_not_equal(p_src, NULL); + memcpy(p_dst, res_p_dst, src_len); + test_free(res_p_dst); + } + check_expected(p_iv); + check_expected(aad_len); + check_expected(p_aad); + check_expected(p_in_mac); + + return (sgx_status_t) mock(); +} + +void expect_sgxsd_ocall_reply(sgx_status_t ocall_res, sgx_status_t res, + const void *p_expected_dst, size_t expected_reply_data_size, + const void *expected_iv, size_t expected_iv_len, + const sgxsd_aes_gcm_mac_t *p_expected_out_mac, + uint64_t expected_tag) { + expect_value(sgxsd_ocall_reply, reply_data_size, expected_reply_data_size); + if (expected_reply_data_size != 0) { + assert_int_not_equal(p_expected_dst, NULL); + expect_memory(sgxsd_ocall_reply, reply_data, p_expected_dst, expected_reply_data_size); + } else { + expect_any(sgxsd_ocall_reply, reply_data); + } + expect_not_value(sgxsd_ocall_reply, reply_header, NULL); + expect_memory(sgxsd_ocall_reply, reply_header->iv.data, expected_iv, expected_iv_len); + expect_memory(sgxsd_ocall_reply, reply_header->mac.data, p_expected_out_mac, sizeof(p_expected_out_mac->data)); + expect_value(sgxsd_ocall_reply, msg_tag.tag, expected_tag); + will_return(sgxsd_ocall_reply, res); + will_return(sgxsd_ocall_reply, ocall_res); +} + +void expect_sgxsd_ocall_noreply(sgx_status_t ocall_res, sgx_status_t res, uint64_t expected_tag) { + expect_value(sgxsd_ocall_reply, reply_data_size, 0); + expect_value(sgxsd_ocall_reply, reply_data, NULL); + expect_value(sgxsd_ocall_reply, reply_header, NULL); + expect_value(sgxsd_ocall_reply, msg_tag.tag, expected_tag); + will_return(sgxsd_ocall_reply, res); + will_return(sgxsd_ocall_reply, ocall_res); +} + +sgx_status_t sgxsd_ocall_reply(sgx_status_t* retval, const sgxsd_msg_header_t *reply_header, + const uint8_t *reply_data, size_t reply_data_size, + sgxsd_msg_tag_t msg_tag) { + check_expected(reply_data_size); + check_expected(reply_data); + check_expected(reply_header); + if (reply_header != NULL) { + check_expected(reply_header->iv.data); + check_expected(reply_header->mac.data); + } + check_expected(msg_tag.tag); + *retval = (sgx_status_t) mock(); + return (sgx_status_t) mock(); +} +sgx_status_t sgxsd_ocall_ra_get_quote(sgx_status_t* retval, + sgx_report_t report, sgx_quote_nonce_t nonce, + const void *vp_get_quote_args, + sgx_report_t* p_qe_report, sgx_quote_t* p_quote, uint32_t quote_size) { + *retval = (sgx_status_t) mock(); + return (sgx_status_t) mock(); +} + +int sgx_is_outside_enclave(const void *addr, size_t size) { + return (int) mock(); +} + +uint32_t sgx_spin_lock(sgx_spinlock_t *lock) { + return 0; +} +uint32_t sgx_spin_unlock(sgx_spinlock_t *lock) { + return 0; +} + +void br_sha256_init(br_sha256_context *sha_context) { +} + +void br_sha256_update(br_sha256_context *sha_context, const void *p_src, size_t src_len) { +} + +void br_sha256_out(const br_sha256_context *sha_handle, void *p_hash) { +} + +sgx_status_t sgx_sha256_close(sgx_sha_state_handle_t sha_handle) { + return (sgx_status_t) SGX_SUCCESS; +} + +sgx_status_t sgx_create_report(const sgx_target_info_t *target_info, const sgx_report_data_t *report_data, sgx_report_t *report) { + return (sgx_status_t) SGX_SUCCESS; +} + +sgx_status_t sgx_verify_report(const sgx_report_t *report) { + return (sgx_status_t) SGX_SUCCESS; +} + +void expect_sgxsd_enclave_server_init(sgx_status_t res, void *expected_args, size_t expected_args_size) { + expect_memory(sgxsd_enclave_server_init, args, expected_args, expected_args_size); + expect_not_value(sgxsd_enclave_server_init, vpp_state, NULL); + will_return(sgxsd_enclave_server_init, res); +} +sgx_status_t sgxsd_enclave_server_init(const sgxsd_server_init_args_t *args, sgxsd_server_state_t **vpp_state) { + check_expected(args); + check_expected(vpp_state); + return (sgx_status_t) mock(); +} + +void expect_sgxsd_enclave_server_handle_call(sgx_status_t res, void *expected_args, size_t expected_args_size, + sgxsd_msg_buf_t expected_msg, sgxsd_msg_from_t expected_from) { + expect_memory(sgxsd_enclave_server_handle_call, args, expected_args, expected_args_size); + expect_value(sgxsd_enclave_server_handle_call, msg.size, expected_msg.size); + if (expected_msg.size != 0) { + assert_int_not_equal(expected_msg.data, NULL); + expect_memory(sgxsd_enclave_server_handle_call, msg.data, expected_msg.data, expected_msg.size); + } else { + expect_any(sgxsd_enclave_server_handle_call, msg.data); + } + expect_memory(sgxsd_enclave_server_handle_call, &from.tag, &expected_from.tag, sizeof(expected_from.tag)); + expect_not_value(sgxsd_enclave_server_handle_call, vpp_state, NULL); + will_return(sgxsd_enclave_server_handle_call, res); +} +sgx_status_t sgxsd_enclave_server_handle_call(const sgxsd_server_handle_call_args_t *args, sgxsd_msg_buf_t msg, + sgxsd_msg_from_t from, sgxsd_server_state_t **vpp_state) { + check_expected(args); + check_expected(msg.size); + check_expected(msg.data); + check_expected(&from.tag); + check_expected(vpp_state); + return (sgx_status_t) mock(); +} + +void expect_sgxsd_enclave_server_terminate(sgx_status_t res, void *expected_args, size_t expected_args_size) { + expect_memory(sgxsd_enclave_server_terminate, args, expected_args, expected_args_size); + expect_any(sgxsd_enclave_server_terminate, vp_state); + will_return(sgxsd_enclave_server_terminate, res); +} +sgx_status_t sgxsd_enclave_server_terminate(const sgxsd_server_terminate_args_t *args, sgxsd_server_state_t *vp_state) { + check_expected(args); + check_expected(vp_state); + return (sgx_status_t) mock(); +} diff --git a/enclave/sgxsd_enclave/sgxsd-enclave.c b/enclave/sgxsd_enclave/sgxsd-enclave.c new file mode 100644 index 0000000..4da8b2a --- /dev/null +++ b/enclave/sgxsd_enclave/sgxsd-enclave.c @@ -0,0 +1,825 @@ +/* + * Copyright (C) 2017 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + /** + * @author Jeff Griffin + */ +#include +#include +#include +#include +#include + +#include "sgx_trts.h" +#include "sgx_quote.h" +#include "sgx_utils.h" +#include "sgx_spinlock.h" +#include "sgx_lfence.h" + +#include "bearssl_aead.h" +#include "bearssl_hash.h" + +#include "sgxsd.h" +#include "sgxsd-enclave.h" + +#if UNIT_TESTING +#include +#include +#include +#include "cmockery.h" +#define memset_s(s, smax, c, n) memset(s, c, n); +#endif + +// +// internal definitions +// + +typedef struct sgxsd_sha256_hash { + uint8_t data[SGXSD_SHA256_HASH_SIZE]; +} sgxsd_sha256_hash_t; + +typedef struct sgxsd_pending_request { + uint64_t id_val; + sgxsd_sha256_hash_t hkdf_prk; +} sgxsd_pending_request_t; + +typedef struct sgxsd_server_state_desc { + bool valid; + sgx_spinlock_t lock; + sgxsd_server_state_t *p_state; +} sgxsd_server_state_desc_t; + +#ifndef SGXSD_ENCLAVE_MAX_SERVERS +#define SGXSD_ENCLAVE_MAX_SERVERS 256 +#endif + +sgx_status_t SGX_CDECL sgxsd_ocall_reply(sgx_status_t* retval, const sgxsd_msg_header_t* reply_header, const uint8_t* reply_data, size_t reply_data_size, sgxsd_msg_tag_t msg_tag); + +sgx_status_t sgxsd_enclave_generate_curve25519_keypair(sgxsd_curve25519_key_pair_t *p_keypair); + +// +// static variables +// + +bool g_sgxsd_enclave_node_initialized = false; +sgx_spinlock_t g_sgxsd_enclave_node_init_lock; + +sgxsd_sha256_hash_t g_sgxsd_enclave_read_rand_state; +sgx_spinlock_t g_sgxsd_enclave_read_rand_lock; + +sgxsd_curve25519_key_pair_t g_sgxsd_enclave_dh_keypair; +sgxsd_curve25519_key_pair_t g_sgxsd_enclave_new_dh_keypair; +sgx_spinlock_t g_sgxsd_enclave_dh_keypair_lock; + +sgxsd_pending_request_t *g_sgxsd_enclave_pending_requests; +uint8_t g_sgxsd_enclave_pending_requests_table_order; +uint64_t g_sgxsd_enclave_last_pending_request_id_val; +sgxsd_aes_gcm_key_t g_sgxsd_enclave_pending_request_id_key; +sgx_spinlock_t g_sgxsd_enclave_pending_requests_lock; + +static const sgxsd_server_state_handle_t g_sgxsd_enclave_max_servers = SGXSD_ENCLAVE_MAX_SERVERS; +sgxsd_server_state_desc_t g_sgxsd_enclave_server_states[SGXSD_ENCLAVE_MAX_SERVERS]; + +static +void sgxsd_spin_lock(sgx_spinlock_t *p_spinlock) { + sgx_spin_lock(p_spinlock); + sgx_lfence(); +} +static +void sgxsd_spin_unlock(sgx_spinlock_t *p_spinlock) { + sgx_spin_unlock(p_spinlock); +} + +sgx_status_t sgxsd_enclave_node_init_locked(const sgxsd_node_init_args_t *p_args); +sgx_status_t sgxsd_enclave_node_init(const sgxsd_node_init_args_t *p_args) { + sgxsd_spin_lock(&g_sgxsd_enclave_node_init_lock); + + sgx_status_t res = sgxsd_enclave_node_init_locked(p_args); + + sgxsd_spin_unlock(&g_sgxsd_enclave_node_init_lock); + return res; +} +sgx_status_t sgxsd_enclave_node_init_locked(const sgxsd_node_init_args_t *p_args) { + if (g_sgxsd_enclave_node_initialized) { + return SGX_ERROR_INVALID_STATE; + } + + // don't allow enclave to be mapped at minimum or maximum of address space + if (1 != sgx_is_outside_enclave(NULL, 0)) { + return SGX_ERROR_UNEXPECTED; + } + if (1 != sgx_is_outside_enclave((void *) UINTPTR_MAX, 0)) { + return SGX_ERROR_UNEXPECTED; + } + + // validate parameters + if (p_args == NULL) { + return SGX_ERROR_INVALID_PARAMETER; + } + if (p_args->pending_requests_table_order > sizeof(uint64_t) * 8 - 1) { + return SGX_ERROR_INVALID_PARAMETER; + } + + // generate unpredictable initial value for private and public keys + sgx_status_t dh_keypair_rand_res = + sgxsd_enclave_generate_curve25519_keypair(&g_sgxsd_enclave_dh_keypair); + if (dh_keypair_rand_res != SGX_SUCCESS) { + return dh_keypair_rand_res; + } + + sgx_status_t new_dh_keypair_rand_res = + sgxsd_enclave_generate_curve25519_keypair(&g_sgxsd_enclave_new_dh_keypair); + if (new_dh_keypair_rand_res != SGX_SUCCESS) { + return new_dh_keypair_rand_res; + } + + sgx_status_t pending_request_id_key_res = + sgx_read_rand((uint8_t *) &g_sgxsd_enclave_pending_request_id_key.data, sizeof(g_sgxsd_enclave_pending_request_id_key.data)); + if (pending_request_id_key_res != SGX_SUCCESS) { + return pending_request_id_key_res; + } + + g_sgxsd_enclave_pending_requests_table_order = p_args->pending_requests_table_order; + g_sgxsd_enclave_pending_requests = calloc((uint64_t){1} << g_sgxsd_enclave_pending_requests_table_order, + sizeof(*g_sgxsd_enclave_pending_requests)); + if (g_sgxsd_enclave_pending_requests == NULL) { + return SGX_ERROR_OUT_OF_MEMORY; + } + + g_sgxsd_enclave_node_initialized = true; + return SGX_SUCCESS; +} + +void __attribute__ ((noinline)) sgxsd_br_clear_stack() { + uint8_t stack[4096]; + memset_s(&stack, sizeof(stack), 0, sizeof(stack)); + _mm256_zeroall(); +} + +#ifndef UNIT_TESTING +static +sgx_status_t sgxsd_aes_gcm_run(bool encrypt, const sgxsd_aes_gcm_key_t *p_key, + const void *p_src, uint32_t src_len, void *p_dst, + const sgxsd_aes_gcm_iv_t *p_iv, + const void *p_aad, uint32_t aad_len, + sgxsd_aes_gcm_mac_t *p_mac) { + if (p_key == NULL || + ((p_src == NULL || p_dst == NULL) && src_len != 0) || + p_iv == NULL || + (p_aad == NULL && aad_len != 0) || + p_mac == NULL) { + return SGX_ERROR_INVALID_PARAMETER; + } + br_aes_x86ni_ctr_keys aes_ctx; + br_aes_x86ni_ctr_init(&aes_ctx, p_key->data, sizeof(p_key->data)); + br_gcm_context aes_gcm_ctx; + br_gcm_init(&aes_gcm_ctx, &aes_ctx.vtable, &br_ghash_pclmul); + br_gcm_reset(&aes_gcm_ctx, p_iv->data, sizeof(p_iv->data)); + if (aad_len != 0) { + br_gcm_aad_inject(&aes_gcm_ctx, p_aad, aad_len); + } + br_gcm_flip(&aes_gcm_ctx); + if (src_len != 0) { + memmove(p_dst, p_src, src_len); + br_gcm_run(&aes_gcm_ctx, encrypt, p_dst, src_len); + } + bool tag_res; + if (encrypt) { + br_gcm_get_tag(&aes_gcm_ctx, p_mac->data); + tag_res = true; + } else { + tag_res = br_gcm_check_tag(&aes_gcm_ctx, p_mac->data); + } + sgxsd_br_clear_stack(); + memset_s(&aes_ctx, sizeof(aes_ctx), 0, sizeof(aes_ctx)); + memset_s(&aes_gcm_ctx, sizeof(aes_gcm_ctx), 0, sizeof(aes_gcm_ctx)); + if (tag_res) { + return SGX_SUCCESS; + } else { + if (p_dst != NULL) { + memset_s(p_dst, src_len, 0, src_len); + } + return SGX_ERROR_MAC_MISMATCH; + } +} + +sgx_status_t sgxsd_aes_gcm_encrypt(const sgxsd_aes_gcm_key_t *p_key, + const void *p_src, uint32_t src_len, void *p_dst, + const sgxsd_aes_gcm_iv_t *p_iv, + const void *p_aad, uint32_t aad_len, + sgxsd_aes_gcm_mac_t *p_out_mac) { + return sgxsd_aes_gcm_run(true, p_key, p_src, src_len, p_dst, p_iv, p_aad, aad_len, p_out_mac); +} +sgx_status_t sgxsd_aes_gcm_decrypt(const sgxsd_aes_gcm_key_t *p_key, + const void *p_src, uint32_t src_len, void *p_dst, + const sgxsd_aes_gcm_iv_t *p_iv, + const void *p_aad, uint32_t aad_len, + const sgxsd_aes_gcm_mac_t *p_in_mac) { + return sgxsd_aes_gcm_run(false, p_key, p_src, src_len, p_dst, p_iv, p_aad, aad_len, (sgxsd_aes_gcm_mac_t *) p_in_mac); +} +#endif + +typedef struct sgxsd_sha256_buf { + const void *data; + uint32_t size; +} sgxsd_sha256_buf_t; + +static +void sgxsd_enclave_sha256(sgxsd_sha256_hash_t *p_hash, uint32_t buf_count, const sgxsd_sha256_buf_t *bufs) { + br_sha256_context sha_context; + br_sha256_init(&sha_context); + for (uint32_t buf_idx = 0; buf_idx < buf_count; buf_idx++) { + br_sha256_update(&sha_context, bufs[buf_idx].data, bufs[buf_idx].size); + } + br_sha256_out(&sha_context, p_hash->data); + memset_s(&sha_context, sizeof(sha_context), 0, sizeof(sha_context)); +} + +void sgxsd_enclave_hmac_sha256(sgxsd_sha256_hash_t *p_hash, uint32_t buf_count, sgxsd_sha256_buf_t *bufs) { + uint8_t i_key_pad[64]; + memset_s(i_key_pad, sizeof(i_key_pad), 0x36, sizeof(i_key_pad)); + uint8_t o_key_pad[64]; + memset_s(o_key_pad, sizeof(o_key_pad), 0x5c, sizeof(o_key_pad)); + + sgxsd_sha256_buf_t *p_key_buf = &bufs[0]; + sgxsd_sha256_hash_t key_hash; + if (p_key_buf->size > sizeof(i_key_pad)) { + sgxsd_enclave_sha256(&key_hash, 1, p_key_buf); + *p_key_buf = (sgxsd_sha256_buf_t) { .data = key_hash.data, .size = sizeof(key_hash.data) }; + } + + // after this point we need to make sure to zero-out i_key_pad and o_key_pad + _Static_assert(sizeof(key_hash.data) <= sizeof(i_key_pad), "i_key_pad overflow"); + for (uint32_t key_idx = 0; key_idx < p_key_buf->size; key_idx++) { + const uint8_t *key_bytes = p_key_buf->data; + i_key_pad[key_idx] ^= key_bytes[key_idx]; + o_key_pad[key_idx] ^= key_bytes[key_idx]; + } + memset_s(&key_hash, sizeof(key_hash), 0, sizeof(key_hash)); + + *p_key_buf = (sgxsd_sha256_buf_t) { .data = i_key_pad, .size = sizeof(i_key_pad) }; + sgxsd_enclave_sha256(p_hash, buf_count, bufs); + sgxsd_enclave_sha256(p_hash, 2, (sgxsd_sha256_buf_t[]) { + { &o_key_pad, sizeof(o_key_pad) }, + { p_hash->data, sizeof(p_hash->data) } + }); + + sgxsd_br_clear_stack(); + memset_s(i_key_pad, sizeof(i_key_pad), 0, sizeof(i_key_pad)); + memset_s(o_key_pad, sizeof(o_key_pad), 0, sizeof(o_key_pad)); +} +typedef struct sgxsd_ra_hkdf_buf { + sgxsd_sha256_hash_t t_n; + uint8_t n; +} sgxsd_ra_hkdf_buf_t; +void sgxsd_enclave_ra_hkdf_round(sgxsd_sha256_hash_t *prk, sgxsd_ra_hkdf_buf_t *buf) { + buf->n++; + sgxsd_sha256_buf_t hmac_data_buf; + if (buf->n == 1) { + hmac_data_buf = (sgxsd_sha256_buf_t) { &buf->n, sizeof(buf->n) }; + } else { + _Static_assert(offsetof(sgxsd_ra_hkdf_buf_t, n) == sizeof(buf->t_n.data), "sgxsd_ra_hkdf_buf_t.n alignment"); + hmac_data_buf = (sgxsd_sha256_buf_t) { buf->t_n.data, sizeof(buf->t_n.data) + sizeof(buf->n) }; + } + sgxsd_enclave_hmac_sha256(&buf->t_n, 2, (sgxsd_sha256_buf_t[]) { + { prk->data, sizeof(prk->data) }, + hmac_data_buf + }); +} + +sgx_status_t sgxsd_enclave_read_rand(sgxsd_rand_buf_t *p_privkey) { + sgx_status_t read_rand_res = sgx_read_rand((uint8_t *) &p_privkey->x, sizeof(p_privkey->x)); + if (read_rand_res != SGX_SUCCESS) { + return read_rand_res; + } + + sgxsd_spin_lock(&g_sgxsd_enclave_read_rand_lock); + + sgxsd_sha256_hash_t hkdf_prk; + sgxsd_enclave_hmac_sha256(&hkdf_prk, 2, (sgxsd_sha256_buf_t[]) { + { &g_sgxsd_enclave_read_rand_state.data, sizeof(g_sgxsd_enclave_read_rand_state.data) }, + { &p_privkey->x, sizeof(p_privkey->x) }, + }); + + sgxsd_ra_hkdf_buf_t hkdf_buf = { .n = 0 }; + sgxsd_enclave_ra_hkdf_round(&hkdf_prk, &hkdf_buf); + g_sgxsd_enclave_read_rand_state = hkdf_buf.t_n; + + sgxsd_enclave_ra_hkdf_round(&hkdf_prk, &hkdf_buf); + memcpy(&p_privkey->x, &hkdf_buf.t_n.data, sizeof(p_privkey->x)); + _Static_assert(sizeof(p_privkey->x) == sizeof(hkdf_buf.t_n.data), "p_privkey overflow"); + + sgxsd_spin_unlock(&g_sgxsd_enclave_read_rand_lock); + + memset_s(&hkdf_buf, sizeof(hkdf_buf), 0, sizeof(hkdf_buf)); + memset_s(&hkdf_prk, sizeof(hkdf_prk), 0, sizeof(hkdf_prk)); + + return SGX_SUCCESS; +} + +int curve25519_donna(uint8_t *, const uint8_t *, const uint8_t *); + +sgx_status_t sgxsd_enclave_generate_curve25519_keypair(sgxsd_curve25519_key_pair_t *p_keypair) { + // generate curve25519 private key + sgx_status_t gen_privkey_res = sgxsd_enclave_read_rand(&p_keypair->privkey); + if (gen_privkey_res != SGX_SUCCESS) { + return gen_privkey_res; + } + p_keypair->privkey.x[0] &= 248; + p_keypair->privkey.x[31] &= 127; + p_keypair->privkey.x[31] |= 64; + + // compute curve25519 public key + static const sgxsd_curve25519_public_key_t BASEPOINT = {{9}}; + curve25519_donna(p_keypair->pubkey.x, p_keypair->privkey.x, BASEPOINT.x); + + return SGX_SUCCESS; +} + +sgx_status_t sgxsd_enclave_get_next_report(sgx_target_info_t qe_target_info, sgx_report_t *p_report) { + if (!g_sgxsd_enclave_node_initialized) { + return SGX_ERROR_INVALID_STATE; + } + + // validate parameters + if (p_report == NULL) { + return SGX_ERROR_INVALID_PARAMETER; + } + + // generate curve25519 + sgxsd_curve25519_key_pair_t new_dh_keypair; + sgx_status_t generate_keypair_res = sgxsd_enclave_generate_curve25519_keypair(&new_dh_keypair); + if (generate_keypair_res != SGX_SUCCESS) { + return generate_keypair_res; + } + + // construct report data with new curve25519 public key + sgx_report_data_t report_data = { .d = { 0 } }; + memcpy(report_data.d, new_dh_keypair.pubkey.x, sizeof(new_dh_keypair.pubkey.x)); + _Static_assert(sizeof(report_data.d) >= sizeof(new_dh_keypair.pubkey.x), "sgx_report_data_t.d overflow"); + + sgx_status_t create_report_res = sgx_create_report(&qe_target_info, &report_data, p_report); + if (create_report_res != SGX_SUCCESS) { + // no need to cleanup new_dh_keypair as a report hasn't been generated to make it useful + return create_report_res; + } + + // move new keypair to transitional global variable + sgxsd_spin_lock(&g_sgxsd_enclave_dh_keypair_lock); + g_sgxsd_enclave_new_dh_keypair = new_dh_keypair; + sgxsd_spin_unlock(&g_sgxsd_enclave_dh_keypair_lock); + memset_s(&new_dh_keypair, sizeof(new_dh_keypair), 0, sizeof(new_dh_keypair)); + + return SGX_SUCCESS; +} + +sgx_status_t sgxsd_enclave_set_current_quote() { + if (!g_sgxsd_enclave_node_initialized) { + return SGX_ERROR_INVALID_STATE; + } + + // copy new keypair to permanent global variable + sgxsd_spin_lock(&g_sgxsd_enclave_dh_keypair_lock); + g_sgxsd_enclave_dh_keypair = g_sgxsd_enclave_new_dh_keypair; + sgxsd_spin_unlock(&g_sgxsd_enclave_dh_keypair_lock); + + return SGX_SUCCESS; +} + +sgx_status_t sgxsd_enclave_add_pending_request(sgxsd_pending_request_id_t *p_pending_request_id, const sgxsd_pending_request_t *p_pending_request) { + uint64_t pending_request_count_mask = ((uint64_t){1} << g_sgxsd_enclave_pending_requests_table_order) - 1; + + sgxsd_spin_lock(&g_sgxsd_enclave_pending_requests_lock); + + g_sgxsd_enclave_last_pending_request_id_val += 1; + uint64_t pending_request_id_val = g_sgxsd_enclave_last_pending_request_id_val; + sgxsd_pending_request_t *p_pending_requests_entry = &g_sgxsd_enclave_pending_requests[pending_request_id_val & pending_request_count_mask]; + p_pending_requests_entry->id_val = pending_request_id_val; + memcpy(&p_pending_requests_entry->hkdf_prk, &p_pending_request->hkdf_prk, sizeof(p_pending_requests_entry->hkdf_prk)); + _Static_assert(sizeof(p_pending_requests_entry->hkdf_prk) == sizeof(p_pending_request->hkdf_prk), "overflow"); + + sgxsd_spin_unlock(&g_sgxsd_enclave_pending_requests_lock); + + sgx_status_t iv_rand_res = sgx_read_rand((uint8_t *) &p_pending_request_id->iv, sizeof(p_pending_request_id->iv)); + if (iv_rand_res != SGX_SUCCESS) { + return iv_rand_res; + } + + sgx_status_t encrypt_res = + sgxsd_aes_gcm_encrypt(&g_sgxsd_enclave_pending_request_id_key, /* p_key */ + &pending_request_id_val, sizeof(pending_request_id_val), /* p_src, src_len */ + &p_pending_request_id->data, /* p_dst */ + &p_pending_request_id->iv, /* p_iv */ + NULL, 0, /* p_aad, aad_len */ + &p_pending_request_id->mac /* p_out_mac */); + _Static_assert(sizeof(pending_request_id_val) == sizeof(p_pending_request_id->data), "pending_request_id overflow"); + if (encrypt_res != SGX_SUCCESS) { + return SGX_ERROR_UNEXPECTED; + } + + return SGX_SUCCESS; +} + +sgx_status_t sgxsd_enclave_remove_pending_request(const sgxsd_pending_request_id_t *p_pending_request_id, sgxsd_pending_request_t *p_pending_request) { + uint64_t pending_request_id_val = 0; + sgx_status_t decrypt_res = + sgxsd_aes_gcm_decrypt(&g_sgxsd_enclave_pending_request_id_key, /* p_key */ + &p_pending_request_id->data, sizeof(p_pending_request_id->data), /* p_src, src_len */ + &pending_request_id_val, /* p_dst */ + &p_pending_request_id->iv, /* p_iv */ + NULL, 0, /* p_aad, aad_len */ + &p_pending_request_id->mac /* p_in_mac */); + _Static_assert(sizeof(p_pending_request_id->data) == sizeof(pending_request_id_val), "pending_request_id_val overflow"); + if (decrypt_res != SGX_SUCCESS) { + return decrypt_res; + } + + uint64_t pending_request_count_mask = ((uint64_t){1} << g_sgxsd_enclave_pending_requests_table_order) - 1; + sgxsd_pending_request_t *p_found_pending_request = + &g_sgxsd_enclave_pending_requests[pending_request_id_val & pending_request_count_mask]; + + sgxsd_spin_lock(&g_sgxsd_enclave_pending_requests_lock); + + sgx_status_t res; + if (p_found_pending_request->id_val == pending_request_id_val) { + *p_pending_request = *p_found_pending_request; + memset_s(p_found_pending_request, sizeof(*p_found_pending_request), 0, sizeof(*p_found_pending_request)); + res = SGX_SUCCESS; + } else { + res = SGXSD_ERROR_PENDING_REQUEST_NOT_FOUND; + } + + sgxsd_spin_unlock(&g_sgxsd_enclave_pending_requests_lock); + return res; +} + +void sgxsd_enclave_derive_request_keys(sgxsd_pending_request_t *p_pending_request, + sgxsd_aes_gcm_key_t *p_client_key, sgxsd_aes_gcm_key_t *p_server_key) { + // generate subkeys using HKDF(PRK) + // HKDF T(1) = client sending AES-GCM key + sgxsd_ra_hkdf_buf_t hkdf_buf = { .n = 0 }; + sgxsd_enclave_ra_hkdf_round(&p_pending_request->hkdf_prk, &hkdf_buf); + if (p_client_key != NULL) { + memmove(p_client_key->data, hkdf_buf.t_n.data, sizeof(p_client_key->data)); + _Static_assert(sizeof(p_client_key->data) <= sizeof(hkdf_buf.t_n.data), "AES key smaller than HKDF output size"); + } + + // HKDF T(2) = client receiving AES-GCM key + sgxsd_enclave_ra_hkdf_round(&p_pending_request->hkdf_prk, &hkdf_buf); + if (p_server_key != NULL) { + memmove(p_server_key->data, hkdf_buf.t_n.data, sizeof(p_server_key->data)); + _Static_assert(sizeof(p_server_key->data) <= sizeof(hkdf_buf.t_n.data), "AES key smaller than HKDF output size"); + } + + // erase HKDF state + memset_s(&hkdf_buf, sizeof(hkdf_buf), 0, sizeof(hkdf_buf)); +} + +sgx_status_t sgxsd_enclave_negotiate_request(const sgxsd_request_negotiation_request_t *p_request, + sgxsd_request_negotiation_response_t *p_response) { + if (!g_sgxsd_enclave_node_initialized) { + return SGX_ERROR_INVALID_STATE; + } + + // validate parameters + if (p_request == NULL) { + return SGX_ERROR_INVALID_PARAMETER; + } + if (p_response == NULL) { + return SGX_ERROR_INVALID_PARAMETER; + } + + // generate ephemeral ecdh keypair + sgxsd_curve25519_key_pair_t server_ephemeral_keypair; + sgx_status_t generate_keypair_res = sgxsd_enclave_generate_curve25519_keypair(&server_ephemeral_keypair); + if (generate_keypair_res != SGX_SUCCESS) { + return generate_keypair_res; + } + + // copy static ecdh keypair + sgxsd_spin_lock(&g_sgxsd_enclave_dh_keypair_lock); + sgxsd_curve25519_key_pair_t server_static_keypair = g_sgxsd_enclave_dh_keypair; + sgxsd_spin_unlock(&g_sgxsd_enclave_dh_keypair_lock); + + // derive ephemeral ecdh shared secret + sgxsd_curve25519_public_key_t ephemeral_dh_key; + curve25519_donna(ephemeral_dh_key.x, server_ephemeral_keypair.privkey.x, p_request->client_pubkey.x); + + // erase ephemeral ecdh private key + memset_s(&server_ephemeral_keypair.privkey, sizeof(server_ephemeral_keypair.privkey), 0, sizeof(server_ephemeral_keypair.privkey)); + + // derive static ecdh shared secret + sgxsd_curve25519_public_key_t static_dh_key; + curve25519_donna(static_dh_key.x, server_static_keypair.privkey.x, p_request->client_pubkey.x); + + // erase static ecdh private key + memset_s(&server_static_keypair.privkey, sizeof(server_static_keypair.privkey), 0, sizeof(server_static_keypair.privkey)); + + // calculate HKDF salt = (client_ephemeral_pubkey || server_ephemeral_pubkey || server_static_pubkey) + sgxsd_sha256_hash_t hkdf_salt; + sgxsd_enclave_sha256(&hkdf_salt, 3, (sgxsd_sha256_buf_t[]) { + { p_request->client_pubkey.x, sizeof(p_request->client_pubkey.x) }, + { server_ephemeral_keypair.pubkey.x, sizeof(server_ephemeral_keypair.pubkey.x) }, + { server_static_keypair.pubkey.x, sizeof(server_static_keypair.pubkey.x) }, + }); + + // derive HKDF PRK (pseudo-random key) from salt and IKM = (ephemeral_dh_secret || static_dh_secret) + sgxsd_pending_request_t pending_request; + sgxsd_enclave_hmac_sha256(&pending_request.hkdf_prk, 3, (sgxsd_sha256_buf_t[]) { + { hkdf_salt.data, sizeof(hkdf_salt.data) }, + { ephemeral_dh_key.x, sizeof(ephemeral_dh_key.x) }, + { static_dh_key.x, sizeof(static_dh_key.x) }, + }); + + // erase ecdh shared secrets and HKDF salt + memset_s(&ephemeral_dh_key, sizeof(ephemeral_dh_key), 0, sizeof(ephemeral_dh_key)); + memset_s(&static_dh_key, sizeof(static_dh_key), 0, sizeof(static_dh_key)); + memset_s(&hkdf_salt, sizeof(hkdf_salt), 0, sizeof(hkdf_salt)); + + // set IV to 0 for the request id encryption in response + memset_s(p_response->encrypted_pending_request_id.iv.data, + sizeof(p_response->encrypted_pending_request_id.iv.data), + 0, + sizeof(p_response->encrypted_pending_request_id.iv.data)); + + // derive server sending AES-GCM key + sgxsd_aes_gcm_key_t server_aes_gcm_key; + sgxsd_enclave_derive_request_keys(&pending_request, NULL, &server_aes_gcm_key); + + // add pending request and have get its assigned ID + sgxsd_pending_request_id_t pending_request_id; + sgx_status_t add_pending_request_res = + sgxsd_enclave_add_pending_request(&pending_request_id, &pending_request); + if (add_pending_request_res != SGX_SUCCESS) { + // erase server sending AES-GCM key + memset_s(&server_aes_gcm_key, sizeof(server_aes_gcm_key), 0, sizeof(server_aes_gcm_key)); + return add_pending_request_res; + } + + // erase HKDF PRK + memset_s(&pending_request.hkdf_prk, sizeof(pending_request.hkdf_prk), 0, sizeof(pending_request.hkdf_prk)); + + // encrypt pending request id + _Static_assert(sizeof(pending_request_id) == sizeof(p_response->encrypted_pending_request_id.data), "sgxsd_request_negotiation_response_t.encrypted_pending_request_id.data overflow"); + sgx_status_t encrypt_pending_request_id_res = + sgxsd_aes_gcm_encrypt(&server_aes_gcm_key, /* p_key */ + &pending_request_id, sizeof(pending_request_id), /* p_src, src_len */ + &p_response->encrypted_pending_request_id.data, /* p_dst */ + &p_response->encrypted_pending_request_id.iv, /* p_iv */ + NULL, 0, /* p_aad, aad_len */ + &p_response->encrypted_pending_request_id.mac /* p_out_mac */); + + // erase server sending AES-GCM key + memset_s(&server_aes_gcm_key, sizeof(server_aes_gcm_key), 0, sizeof(server_aes_gcm_key)); + + if (encrypt_pending_request_id_res != SGX_SUCCESS) { + return SGX_ERROR_UNEXPECTED; + } + + // fill in rest of response fields + p_response->server_static_pubkey = server_static_keypair.pubkey; + p_response->server_ephemeral_pubkey = server_ephemeral_keypair.pubkey; + + return SGX_SUCCESS; +} + +sgx_status_t sgxsd_enclave_server_reply_noerase(sgxsd_msg_buf_t reply_buf, const sgxsd_msg_from_t *p_from); +sgx_status_t sgxsd_enclave_server_reply(sgxsd_msg_buf_t reply_buf, sgxsd_msg_from_t *p_from) { + sgx_status_t res = sgxsd_enclave_server_reply_noerase(reply_buf, p_from); + if (reply_buf.data != NULL) { + memset_s(reply_buf.data, reply_buf.size, 0, reply_buf.size); + } + if (p_from != NULL) { + memset_s(p_from, sizeof(*p_from), 0, sizeof(*p_from)); + } + return res; +} +sgx_status_t sgxsd_enclave_server_reply_noerase(sgxsd_msg_buf_t reply_buf, const sgxsd_msg_from_t *p_from) { + if (p_from == NULL) { + return SGX_ERROR_INVALID_PARAMETER; + } + if (!p_from->valid) { + return SGX_ERROR_INVALID_STATE; + } + + sgxsd_msg_header_t reply_header; + if (reply_buf.data == NULL && reply_buf.size != 0) { + return SGX_ERROR_INVALID_PARAMETER; + } + + // generate random IV for the reply message encryption + sgx_status_t iv_rand_res = sgx_read_rand(reply_header.iv.data, sizeof(reply_header.iv.data)); + if (iv_rand_res != SGX_SUCCESS) { + return iv_rand_res; + } + + // set one bit of IV to 1 for the reply message encryption, to prevent collision with request negotiation response IV=0 + reply_header.iv.data[0] |= 1; + + // encrypt the reply message + sgx_status_t encrypt_res = + sgxsd_aes_gcm_encrypt(&p_from->server_key, /* p_key */ + reply_buf.data, reply_buf.size, /* p_src, src_len */ + reply_buf.data, /* p_dst */ + &reply_header.iv, /* p_iv */ + NULL, 0, /* p_aad, aad_len */ + &reply_header.mac /* p_out_mac */); + if (encrypt_res != SGX_SUCCESS) { + return SGX_ERROR_UNEXPECTED; + } + + // send encrypted reply to the untrusted code + sgx_status_t reply_res; + sgx_status_t reply_ocall_res = + sgxsd_ocall_reply(&reply_res, &reply_header, reply_buf.data, reply_buf.size, p_from->tag); + if (reply_ocall_res == SGX_SUCCESS) { + return reply_res; + } else { + return reply_ocall_res; + } +} + +sgx_status_t sgxsd_enclave_server_noreply(sgxsd_msg_from_t *p_from) { + if (p_from == NULL) { + return SGX_ERROR_INVALID_PARAMETER; + } + if (!p_from->valid) { + return SGX_ERROR_INVALID_STATE; + } + sgx_status_t reply_res; + sgx_status_t reply_ocall_res = + sgxsd_ocall_reply(&reply_res, NULL, NULL, 0, p_from->tag); + memset_s(p_from, sizeof(*p_from), 0, sizeof(*p_from)); + if (reply_ocall_res == SGX_SUCCESS) { + return reply_res; + } else { + return reply_ocall_res; + } +} + +sgx_status_t sgxsd_enclave_server_start_locked(const sgxsd_server_init_args_t *p_args, sgxsd_server_state_desc_t *p_state_desc); +sgx_status_t sgxsd_enclave_server_start(const sgxsd_server_init_args_t *p_args, sgxsd_server_state_handle_t state_handle) { + if (!g_sgxsd_enclave_node_initialized) { + return SGX_ERROR_INVALID_STATE; + } + if (state_handle >= g_sgxsd_enclave_max_servers) { + return SGX_ERROR_INVALID_PARAMETER; + } + + sgxsd_server_state_desc_t *p_state_desc = &g_sgxsd_enclave_server_states[state_handle]; + sgxsd_spin_lock(&p_state_desc->lock); + + sgx_status_t res = sgxsd_enclave_server_start_locked(p_args, p_state_desc); + + sgxsd_spin_unlock(&p_state_desc->lock); + return res; +} +sgx_status_t sgxsd_enclave_server_start_locked(const sgxsd_server_init_args_t *p_args, sgxsd_server_state_desc_t *p_state_desc) { + if (p_state_desc->valid) { + return SGX_ERROR_INVALID_STATE; + } + sgx_status_t init_res = sgxsd_enclave_server_init(p_args, &p_state_desc->p_state); + if (init_res == SGX_SUCCESS) { + p_state_desc->valid = true; + return SGX_SUCCESS; + } else { + return init_res; + } +} + +sgx_status_t sgxsd_enclave_server_call_locked(const sgxsd_server_handle_call_args_t *p_args, + const sgxsd_msg_header_t *p_msg_header, + uint8_t *msg_data, size_t msg_data_size, + sgxsd_msg_tag_t msg_tag, sgxsd_server_state_desc_t *p_state_desc); +sgx_status_t sgxsd_enclave_server_call(const sgxsd_server_handle_call_args_t *p_args, + const sgxsd_msg_header_t *p_msg_header, + uint8_t *msg_data, size_t msg_data_size, + sgxsd_msg_tag_t msg_tag, sgxsd_server_state_handle_t state_handle) { + if (!g_sgxsd_enclave_node_initialized) { + return SGX_ERROR_INVALID_STATE; + } + if (state_handle >= g_sgxsd_enclave_max_servers) { + return SGX_ERROR_INVALID_PARAMETER; + } + sgxsd_server_state_desc_t *p_state_desc = &g_sgxsd_enclave_server_states[state_handle]; + sgxsd_spin_lock(&p_state_desc->lock); + + sgx_status_t res = + sgxsd_enclave_server_call_locked(p_args, p_msg_header, msg_data, msg_data_size, msg_tag, p_state_desc); + + sgxsd_spin_unlock(&p_state_desc->lock); + return res; +} +sgx_status_t sgxsd_enclave_server_call_locked(const sgxsd_server_handle_call_args_t *p_args, + const sgxsd_msg_header_t *p_msg_header, + uint8_t *msg_data, size_t msg_data_size, + sgxsd_msg_tag_t msg_tag, sgxsd_server_state_desc_t *p_state_desc) { + if (!p_state_desc->valid) { + return SGX_ERROR_INVALID_STATE; + } + if (p_msg_header == NULL) { + return SGX_ERROR_INVALID_PARAMETER; + } + if (msg_data == NULL || msg_data_size == 0) { + return SGX_ERROR_INVALID_PARAMETER; + } + + // get pending request by ID + sgxsd_pending_request_t pending_request; + sgx_status_t remove_pending_request_res = sgxsd_enclave_remove_pending_request(&p_msg_header->pending_request_id, &pending_request); + if (remove_pending_request_res != SGX_SUCCESS) { + return remove_pending_request_res; + } + + // derive server and client sending AES-GCM keys + sgxsd_aes_gcm_key_t client_key; + sgxsd_msg_from_t msg_from = { + .valid = true, + .tag = msg_tag, + }; + sgxsd_enclave_derive_request_keys(&pending_request, &client_key, &msg_from.server_key); + + // erase HKDF PRK + memset_s(&pending_request, sizeof(pending_request), 0, sizeof(pending_request)); + + // message is decrypted in-place + sgxsd_msg_buf_t decrypted_msg = { + .data = msg_data, + .size = msg_data_size, + }; + // validate and decrypt the message + sgx_status_t decrypt_msg_res = + sgxsd_aes_gcm_decrypt(&client_key, /* p_key */ + msg_data, msg_data_size, /* p_src, src_len */ + decrypted_msg.data, /* p_dst */ + &p_msg_header->iv, /* p_iv */ + &p_msg_header->pending_request_id, /* p_aad */ + sizeof(p_msg_header->pending_request_id), /* aad_len */ + &p_msg_header->mac /* p_in_mac */); + + // erase client sending AES-GCM key + memset_s(&client_key, sizeof(client_key), 0, sizeof(client_key)); + + if (decrypt_msg_res != SGX_SUCCESS) { + // erase copy of plaintext ticket keys on stack + memset_s(&msg_from, sizeof(msg_from), 0, sizeof(msg_from)); + if (decrypt_msg_res == SGX_ERROR_INVALID_PARAMETER) { + return SGX_ERROR_UNEXPECTED; + } + return decrypt_msg_res; + } + + // call the server_handle_call callback + sgx_status_t server_call_res = + sgxsd_enclave_server_handle_call(p_args, decrypted_msg, msg_from, &p_state_desc->p_state); + + // erase the decrypted message data + memset_s(decrypted_msg.data, decrypted_msg.size, 0, decrypted_msg.size); + + // erase copy of plaintext ticket keys on stack + memset_s(&msg_from, sizeof(msg_from), 0, sizeof(msg_from)); + + return server_call_res; +} + +sgx_status_t sgxsd_enclave_server_stop_locked(const sgxsd_server_terminate_args_t *p_args, sgxsd_server_state_desc_t *p_state_desc); +sgx_status_t sgxsd_enclave_server_stop(const sgxsd_server_terminate_args_t *p_args, sgxsd_server_state_handle_t state_handle) { + if (!g_sgxsd_enclave_node_initialized) { + return SGX_ERROR_INVALID_STATE; + } + if (state_handle >= g_sgxsd_enclave_max_servers) { + return SGX_ERROR_INVALID_PARAMETER; + } + sgxsd_server_state_desc_t *p_state_desc = &g_sgxsd_enclave_server_states[state_handle]; + sgxsd_spin_lock(&p_state_desc->lock); + + sgx_status_t res = sgxsd_enclave_server_stop_locked(p_args, p_state_desc); + + sgxsd_spin_unlock(&p_state_desc->lock); + return res; +} +sgx_status_t sgxsd_enclave_server_stop_locked(const sgxsd_server_terminate_args_t *p_args, sgxsd_server_state_desc_t *p_state_desc) { + if (!p_state_desc->valid) { + return SGX_ERROR_INVALID_STATE; + } + + sgxsd_server_state_t *p_state = p_state_desc->p_state; + // zero out old state to prevent replay / rewind + memset_s(p_state_desc, sizeof(*p_state_desc), 0, sizeof(*p_state_desc)); + + return sgxsd_enclave_server_terminate(p_args, p_state); +} diff --git a/enclave/sgxsd_ffi/.cargo/config b/enclave/sgxsd_ffi/.cargo/config new file mode 100644 index 0000000..fe9c8d6 --- /dev/null +++ b/enclave/sgxsd_ffi/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/target" diff --git a/enclave/sgxsd_ffi/Cargo.toml b/enclave/sgxsd_ffi/Cargo.toml new file mode 100644 index 0000000..3ceb8e4 --- /dev/null +++ b/enclave/sgxsd_ffi/Cargo.toml @@ -0,0 +1,34 @@ +[package] +authors = ["Open Whisper Systems"] +name = "sgxsd_ffi" +version = "0.1.0" +license = "AGPL-3.0-or-later" +edition = "2018" + +[features] +default = [] +test = ["lazy_static", "mockers", "mockers_derive", "rand", "test_ffi"] + +[dependencies] +libc = { version = "0.2", default-features = false, features = [] } +rand_core = { version = "0.5", default-features = false, features = [] } +num-traits = { version = "0.2", default-features = false, features = [] } +sgx_ffi = { path = "../sgx_ffi" } + +# also need these as optional dependencies for the "test" feature +lazy_static = { version = "1.4", optional = true } +mockers = { version = "0.21", optional = true } +mockers_derive = { version = "0.21", optional = true } +rand = { version = "0.7", optional = true, default-features = false, features = [] } +test_ffi = { path = "../test_ffi", optional = true } + +[dev-dependencies] +lazy_static = "1.4" +mockers = "0.21" +mockers_derive = "0.21" +rand = { version = "0.7", default-features = false, features = [] } +sgx_ffi = { path = "../sgx_ffi", features = ["test"] } +test_ffi = { path = "../test_ffi" } + +[lib] +doctest = false diff --git a/enclave/sgxsd_ffi/src/bindgen_wrapper.h b/enclave/sgxsd_ffi/src/bindgen_wrapper.h new file mode 100644 index 0000000..c874d56 --- /dev/null +++ b/enclave/sgxsd_ffi/src/bindgen_wrapper.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include "sgxsd-enclave.h" +#include "bearssl_hash.h" +#include "curve25519-donna.h" diff --git a/enclave/sgxsd_ffi/src/bindgen_wrapper.rs b/enclave/sgxsd_ffi/src/bindgen_wrapper.rs new file mode 100644 index 0000000..c4572cd --- /dev/null +++ b/enclave/sgxsd_ffi/src/bindgen_wrapper.rs @@ -0,0 +1,4236 @@ +/* automatically generated by rust-bindgen */ + +#[repr(C)] +#[derive(Default)] +pub struct __IncompleteArrayField(::core::marker::PhantomData, [T; 0]); +impl __IncompleteArrayField { + #[inline] + pub const fn new() -> Self { + __IncompleteArrayField(::core::marker::PhantomData, []) + } + #[inline] + pub unsafe fn as_ptr(&self) -> *const T { + ::core::mem::transmute(self) + } + #[inline] + pub unsafe fn as_mut_ptr(&mut self) -> *mut T { + ::core::mem::transmute(self) + } + #[inline] + pub unsafe fn as_slice(&self, len: usize) -> &[T] { + ::core::slice::from_raw_parts(self.as_ptr(), len) + } + #[inline] + pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { + ::core::slice::from_raw_parts_mut(self.as_mut_ptr(), len) + } +} +impl ::core::fmt::Debug for __IncompleteArrayField { + fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + fmt.write_str("__IncompleteArrayField") + } +} +impl ::core::clone::Clone for __IncompleteArrayField { + #[inline] + fn clone(&self) -> Self { + Self::new() + } +} +pub const false_: u32 = 0; +pub const true_: u32 = 1; +pub const __bool_true_false_are_defined: u32 = 1; +pub const EXIT_FAILURE: u32 = 1; +pub const EXIT_SUCCESS: u32 = 0; +pub const RAND_MAX: u32 = 2147483647; +pub const MB_CUR_MAX: u32 = 1; +pub const INT8_MIN: i32 = -128; +pub const INT16_MIN: i32 = -32768; +pub const INT32_MIN: i32 = -2147483648; +pub const INT64_MIN: i64 = -9223372036854775808; +pub const INT8_MAX: u32 = 127; +pub const INT16_MAX: u32 = 32767; +pub const INT32_MAX: u32 = 2147483647; +pub const INT64_MAX: u64 = 9223372036854775807; +pub const UINT8_MAX: u32 = 255; +pub const UINT16_MAX: u32 = 65535; +pub const UINT32_MAX: u32 = 4294967295; +pub const UINT64_MAX: i32 = -1; +pub const INT_LEAST8_MIN: i32 = -128; +pub const INT_LEAST16_MIN: i32 = -32768; +pub const INT_LEAST32_MIN: i32 = -2147483648; +pub const INT_LEAST64_MIN: i64 = -9223372036854775808; +pub const INT_LEAST8_MAX: u32 = 127; +pub const INT_LEAST16_MAX: u32 = 32767; +pub const INT_LEAST32_MAX: u32 = 2147483647; +pub const INT_LEAST64_MAX: u64 = 9223372036854775807; +pub const UINT_LEAST8_MAX: u32 = 255; +pub const UINT_LEAST16_MAX: u32 = 65535; +pub const UINT_LEAST32_MAX: u32 = 4294967295; +pub const UINT_LEAST64_MAX: i32 = -1; +pub const INT_FAST8_MIN: i32 = -128; +pub const INT_FAST16_MIN: i32 = -32768; +pub const INT_FAST32_MIN: i32 = -2147483648; +pub const INT_FAST64_MIN: i64 = -9223372036854775808; +pub const INT_FAST8_MAX: u32 = 127; +pub const INT_FAST16_MAX: u64 = 9223372036854775807; +pub const INT_FAST32_MAX: u64 = 9223372036854775807; +pub const INT_FAST64_MAX: u64 = 9223372036854775807; +pub const UINT_FAST8_MAX: u32 = 255; +pub const UINT_FAST16_MAX: i32 = -1; +pub const UINT_FAST32_MAX: i32 = -1; +pub const UINT_FAST64_MAX: i32 = -1; +pub const INTPTR_MIN: i64 = -9223372036854775808; +pub const INTPTR_MAX: u64 = 9223372036854775807; +pub const UINTPTR_MAX: i32 = -1; +pub const INTMAX_MIN: i64 = -9223372036854775808; +pub const INTMAX_MAX: u64 = 9223372036854775807; +pub const UINTMAX_MAX: i32 = -1; +pub const PTRDIFF_MIN: i64 = -9223372036854775808; +pub const PTRDIFF_MAX: u64 = 9223372036854775807; +pub const SIZE_MAX: i32 = -1; +pub const WINT_MIN: u32 = 0; +pub const WINT_MAX: u32 = 4294967295; +pub const SGX_FLAGS_INITTED: u32 = 1; +pub const SGX_FLAGS_DEBUG: u32 = 2; +pub const SGX_FLAGS_MODE64BIT: u32 = 4; +pub const SGX_FLAGS_PROVISION_KEY: u32 = 16; +pub const SGX_FLAGS_EINITTOKEN_KEY: u32 = 32; +pub const SGX_FLAGS_KSS: u32 = 128; +pub const SGX_XFRM_LEGACY: u32 = 3; +pub const SGX_XFRM_AVX: u32 = 6; +pub const SGX_XFRM_AVX512: u32 = 230; +pub const SGX_XFRM_MPX: u32 = 24; +pub const SGX_XFRM_RESERVED: i32 = -232; +pub const SGX_KEYSELECT_EINITTOKEN: u32 = 0; +pub const SGX_KEYSELECT_PROVISION: u32 = 1; +pub const SGX_KEYSELECT_PROVISION_SEAL: u32 = 2; +pub const SGX_KEYSELECT_REPORT: u32 = 3; +pub const SGX_KEYSELECT_SEAL: u32 = 4; +pub const SGX_KEYPOLICY_MRENCLAVE: u32 = 1; +pub const SGX_KEYPOLICY_MRSIGNER: u32 = 2; +pub const SGX_KEYPOLICY_NOISVPRODID: u32 = 4; +pub const SGX_KEYPOLICY_CONFIGID: u32 = 8; +pub const SGX_KEYPOLICY_ISVFAMILYID: u32 = 16; +pub const SGX_KEYPOLICY_ISVEXTPRODID: u32 = 32; +pub const SGX_KEYID_SIZE: u32 = 32; +pub const SGX_CPUSVN_SIZE: u32 = 16; +pub const SGX_CONFIGID_SIZE: u32 = 64; +pub const SGX_KEY_REQUEST_RESERVED2_BYTES: u32 = 434; +pub const SGX_HASH_SIZE: u32 = 32; +pub const SGX_MAC_SIZE: u32 = 16; +pub const SGX_REPORT_DATA_SIZE: u32 = 64; +pub const SGX_ISVEXT_PROD_ID_SIZE: u32 = 16; +pub const SGX_ISV_FAMILY_ID_SIZE: u32 = 16; +pub const SGX_TARGET_INFO_RESERVED1_BYTES: u32 = 2; +pub const SGX_TARGET_INFO_RESERVED2_BYTES: u32 = 8; +pub const SGX_TARGET_INFO_RESERVED3_BYTES: u32 = 384; +pub const SGX_REPORT_BODY_RESERVED1_BYTES: u32 = 12; +pub const SGX_REPORT_BODY_RESERVED2_BYTES: u32 = 32; +pub const SGX_REPORT_BODY_RESERVED3_BYTES: u32 = 32; +pub const SGX_REPORT_BODY_RESERVED4_BYTES: u32 = 42; +pub const MAX_EX_FEATURES_COUNT: u32 = 32; +pub const SGX_CREATE_ENCLAVE_EX_PCL_BIT_IDX: u32 = 0; +pub const SGX_CREATE_ENCLAVE_EX_PCL: u32 = 1; +pub const SGX_CREATE_ENCLAVE_EX_SWITCHLESS_BIT_IDX: u32 = 1; +pub const SGX_CREATE_ENCLAVE_EX_SWITCHLESS: u32 = 2; +pub const SGX_CREATE_ENCLAVE_EX_KSS_BIT_IDX: u32 = 2; +pub const SGX_CREATE_ENCLAVE_EX_KSS: u32 = 4; +pub const _SGX_LAST_EX_FEATURE_IDX_: u32 = 2; +pub const SGX_DEBUG_FLAG: u32 = 1; +pub const SGX_PLATFORM_INFO_SIZE: u32 = 101; +pub const SGXSD_AES_GCM_IV_SIZE: u32 = 12; +pub const SGXSD_AES_GCM_MAC_SIZE: u32 = 16; +pub const SGXSD_AES_GCM_KEY_SIZE: u32 = 32; +pub const SGXSD_CURVE25519_KEY_SIZE: u32 = 32; +pub const SGXSD_SHA256_HASH_SIZE: u32 = 32; +pub const BR_HASHDESC_ID_OFF: u32 = 0; +pub const BR_HASHDESC_ID_MASK: u32 = 255; +pub const BR_HASHDESC_OUT_OFF: u32 = 8; +pub const BR_HASHDESC_OUT_MASK: u32 = 127; +pub const BR_HASHDESC_STATE_OFF: u32 = 15; +pub const BR_HASHDESC_STATE_MASK: u32 = 255; +pub const BR_HASHDESC_LBLEN_OFF: u32 = 23; +pub const BR_HASHDESC_LBLEN_MASK: u32 = 15; +pub const br_md5_ID: u32 = 1; +pub const br_md5_SIZE: u32 = 16; +pub const br_sha1_ID: u32 = 2; +pub const br_sha1_SIZE: u32 = 20; +pub const br_sha224_ID: u32 = 3; +pub const br_sha224_SIZE: u32 = 28; +pub const br_sha256_ID: u32 = 4; +pub const br_sha256_SIZE: u32 = 32; +pub const br_sha384_ID: u32 = 5; +pub const br_sha384_SIZE: u32 = 48; +pub const br_sha512_ID: u32 = 6; +pub const br_sha512_SIZE: u32 = 64; +pub const br_md5sha1_ID: u32 = 0; +pub const br_md5sha1_SIZE: u32 = 36; +pub const SGX_SUCCESS: _status_t = 0; +pub const SGX_ERROR_UNEXPECTED: _status_t = 1; +pub const SGX_ERROR_INVALID_PARAMETER: _status_t = 2; +pub const SGX_ERROR_OUT_OF_MEMORY: _status_t = 3; +pub const SGX_ERROR_ENCLAVE_LOST: _status_t = 4; +pub const SGX_ERROR_INVALID_STATE: _status_t = 5; +pub const SGX_ERROR_FEATURE_NOT_SUPPORTED: _status_t = 8; +pub const SGX_ERROR_INVALID_FUNCTION: _status_t = 4097; +pub const SGX_ERROR_OUT_OF_TCS: _status_t = 4099; +pub const SGX_ERROR_ENCLAVE_CRASHED: _status_t = 4102; +pub const SGX_ERROR_ECALL_NOT_ALLOWED: _status_t = 4103; +pub const SGX_ERROR_OCALL_NOT_ALLOWED: _status_t = 4104; +pub const SGX_ERROR_STACK_OVERRUN: _status_t = 4105; +pub const SGX_ERROR_UNDEFINED_SYMBOL: _status_t = 8192; +pub const SGX_ERROR_INVALID_ENCLAVE: _status_t = 8193; +pub const SGX_ERROR_INVALID_ENCLAVE_ID: _status_t = 8194; +pub const SGX_ERROR_INVALID_SIGNATURE: _status_t = 8195; +pub const SGX_ERROR_NDEBUG_ENCLAVE: _status_t = 8196; +pub const SGX_ERROR_OUT_OF_EPC: _status_t = 8197; +pub const SGX_ERROR_NO_DEVICE: _status_t = 8198; +pub const SGX_ERROR_MEMORY_MAP_CONFLICT: _status_t = 8199; +pub const SGX_ERROR_INVALID_METADATA: _status_t = 8201; +pub const SGX_ERROR_DEVICE_BUSY: _status_t = 8204; +pub const SGX_ERROR_INVALID_VERSION: _status_t = 8205; +pub const SGX_ERROR_MODE_INCOMPATIBLE: _status_t = 8206; +pub const SGX_ERROR_ENCLAVE_FILE_ACCESS: _status_t = 8207; +pub const SGX_ERROR_INVALID_MISC: _status_t = 8208; +pub const SGX_ERROR_INVALID_LAUNCH_TOKEN: _status_t = 8209; +pub const SGX_ERROR_MAC_MISMATCH: _status_t = 12289; +pub const SGX_ERROR_INVALID_ATTRIBUTE: _status_t = 12290; +pub const SGX_ERROR_INVALID_CPUSVN: _status_t = 12291; +pub const SGX_ERROR_INVALID_ISVSVN: _status_t = 12292; +pub const SGX_ERROR_INVALID_KEYNAME: _status_t = 12293; +pub const SGX_ERROR_SERVICE_UNAVAILABLE: _status_t = 16385; +pub const SGX_ERROR_SERVICE_TIMEOUT: _status_t = 16386; +pub const SGX_ERROR_AE_INVALID_EPIDBLOB: _status_t = 16387; +pub const SGX_ERROR_SERVICE_INVALID_PRIVILEGE: _status_t = 16388; +pub const SGX_ERROR_EPID_MEMBER_REVOKED: _status_t = 16389; +pub const SGX_ERROR_UPDATE_NEEDED: _status_t = 16390; +pub const SGX_ERROR_NETWORK_FAILURE: _status_t = 16391; +pub const SGX_ERROR_AE_SESSION_INVALID: _status_t = 16392; +pub const SGX_ERROR_BUSY: _status_t = 16394; +pub const SGX_ERROR_MC_NOT_FOUND: _status_t = 16396; +pub const SGX_ERROR_MC_NO_ACCESS_RIGHT: _status_t = 16397; +pub const SGX_ERROR_MC_USED_UP: _status_t = 16398; +pub const SGX_ERROR_MC_OVER_QUOTA: _status_t = 16399; +pub const SGX_ERROR_KDF_MISMATCH: _status_t = 16401; +pub const SGX_ERROR_UNRECOGNIZED_PLATFORM: _status_t = 16402; +pub const SGX_ERROR_UNSUPPORTED_CONFIG: _status_t = 16403; +pub const SGX_ERROR_NO_PRIVILEGE: _status_t = 20482; +pub const SGX_ERROR_PCL_ENCRYPTED: _status_t = 24577; +pub const SGX_ERROR_PCL_NOT_ENCRYPTED: _status_t = 24578; +pub const SGX_ERROR_PCL_MAC_MISMATCH: _status_t = 24579; +pub const SGX_ERROR_PCL_SHA_MISMATCH: _status_t = 24580; +pub const SGX_ERROR_PCL_GUID_MISMATCH: _status_t = 24581; +pub const SGX_ERROR_FILE_BAD_STATUS: _status_t = 28673; +pub const SGX_ERROR_FILE_NO_KEY_ID: _status_t = 28674; +pub const SGX_ERROR_FILE_NAME_MISMATCH: _status_t = 28675; +pub const SGX_ERROR_FILE_NOT_SGX_FILE: _status_t = 28676; +pub const SGX_ERROR_FILE_CANT_OPEN_RECOVERY_FILE: _status_t = 28677; +pub const SGX_ERROR_FILE_CANT_WRITE_RECOVERY_FILE: _status_t = 28678; +pub const SGX_ERROR_FILE_RECOVERY_NEEDED: _status_t = 28679; +pub const SGX_ERROR_FILE_FLUSH_FAILED: _status_t = 28680; +pub const SGX_ERROR_FILE_CLOSE_FAILED: _status_t = 28681; +pub const SGX_ERROR_UNSUPPORTED_ATT_KEY_ID: _status_t = 32769; +pub const SGX_ERROR_ATT_KEY_CERTIFICATION_FAILURE: _status_t = 32770; +pub const SGX_ERROR_ATT_KEY_UNINITIALIZED: _status_t = 32771; +pub const SGX_ERROR_INVALID_ATT_KEY_CERT_DATA: _status_t = 32772; +pub const SGX_ERROR_PLATFORM_CERT_UNAVAILABLE: _status_t = 32773; +pub const SGX_INTERNAL_ERROR_ENCLAVE_CREATE_INTERRUPTED: _status_t = 61441; +pub type _status_t = u32; +pub use self::_status_t as sgx_status_t; +pub type __int8_t = libc::c_schar; +pub type __uint8_t = libc::c_uchar; +pub type __int16_t = libc::c_short; +pub type __uint16_t = libc::c_ushort; +pub type __int32_t = libc::c_int; +pub type __uint32_t = libc::c_uint; +pub type __int64_t = libc::c_long; +pub type __uint64_t = libc::c_ulong; +pub type __int_least8_t = __int8_t; +pub type __uint_least8_t = __uint8_t; +pub type __int_least16_t = __int16_t; +pub type __uint_least16_t = __uint16_t; +pub type __int_least32_t = __int32_t; +pub type __uint_least32_t = __uint32_t; +pub type __int_least64_t = __int64_t; +pub type __uint_least64_t = __uint64_t; +pub type __int_fast8_t = __int8_t; +pub type __uint_fast8_t = __uint8_t; +pub type __int_fast16_t = libc::c_long; +pub type __uint_fast16_t = libc::c_ulong; +pub type __int_fast32_t = libc::c_long; +pub type __uint_fast32_t = libc::c_ulong; +pub type __int_fast64_t = libc::c_long; +pub type __uint_fast64_t = libc::c_ulong; +pub type __off_t = libc::c_long; +pub type __intptr_t = __int64_t; +pub type __uintptr_t = __uint64_t; +pub type __ptrdiff_t = __int64_t; +pub type __size_t = libc::c_ulong; +pub type __ssize_t = libc::c_long; +pub type __double_t = f64; +pub type __float_t = f32; +pub type __clock_t = libc::c_long; +pub type __time_t = libc::c_long; +pub type __va_list = __builtin_va_list; +pub type __wint_t = libc::c_uint; +pub type __wctype_t = libc::c_ulong; +pub type __wctrans_t = *mut libc::c_int; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct __mbstate_t { + pub __c: libc::c_int, + pub __v: __mbstate_t__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union __mbstate_t__bindgen_ty_1 { + pub __wc: __wint_t, + pub __wcb: [libc::c_char; 4usize], + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout___mbstate_t__bindgen_ty_1() { + assert_eq!( + ::core::mem::size_of::<__mbstate_t__bindgen_ty_1>(), + 4usize, + concat!("Size of: ", stringify!(__mbstate_t__bindgen_ty_1)) + ); + assert_eq!( + ::core::mem::align_of::<__mbstate_t__bindgen_ty_1>(), + 4usize, + concat!("Alignment of ", stringify!(__mbstate_t__bindgen_ty_1)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__mbstate_t__bindgen_ty_1>())).__wc as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t__bindgen_ty_1), + "::", + stringify!(__wc) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<__mbstate_t__bindgen_ty_1>())).__wcb as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t__bindgen_ty_1), + "::", + stringify!(__wcb) + ) + ); +} +impl Default for __mbstate_t__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout___mbstate_t() { + assert_eq!( + ::core::mem::size_of::<__mbstate_t>(), + 8usize, + concat!("Size of: ", stringify!(__mbstate_t)) + ); + assert_eq!( + ::core::mem::align_of::<__mbstate_t>(), + 4usize, + concat!("Alignment of ", stringify!(__mbstate_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__mbstate_t>())).__c as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t), + "::", + stringify!(__c) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__mbstate_t>())).__v as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t), + "::", + stringify!(__v) + ) + ); +} +impl Default for __mbstate_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type __intmax_t = __int64_t; +pub type __uintmax_t = __uint64_t; +pub type va_list = __va_list; +pub type wchar_t = libc::c_int; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct div_t { + pub quot: libc::c_int, + pub rem: libc::c_int, +} +#[test] +fn bindgen_test_layout_div_t() { + assert_eq!( + ::core::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(div_t)) + ); + assert_eq!( + ::core::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(div_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(div_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).rem as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(div_t), + "::", + stringify!(rem) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct ldiv_t { + pub quot: libc::c_long, + pub rem: libc::c_long, +} +#[test] +fn bindgen_test_layout_ldiv_t() { + assert_eq!( + ::core::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(ldiv_t)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(ldiv_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(ldiv_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).rem as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(ldiv_t), + "::", + stringify!(rem) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct lldiv_t { + pub quot: libc::c_longlong, + pub rem: libc::c_longlong, +} +#[test] +fn bindgen_test_layout_lldiv_t() { + assert_eq!( + ::core::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(lldiv_t)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(lldiv_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(lldiv_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).rem as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(lldiv_t), + "::", + stringify!(rem) + ) + ); +} +extern "C" { + pub fn abort(); +} +extern "C" { + pub fn atexit(arg1: ::core::option::Option) -> libc::c_int; +} +extern "C" { + pub fn abs(arg1: libc::c_int) -> libc::c_int; +} +extern "C" { + pub fn atof(arg1: *const libc::c_char) -> f64; +} +extern "C" { + pub fn atoi(arg1: *const libc::c_char) -> libc::c_int; +} +extern "C" { + pub fn atol(arg1: *const libc::c_char) -> libc::c_long; +} +extern "C" { + pub fn bsearch( + arg1: *const libc::c_void, + arg2: *const libc::c_void, + arg3: usize, + arg4: usize, + arg5: ::core::option::Option< + unsafe extern "C" fn( + arg1: *const libc::c_void, + arg2: *const libc::c_void, + ) -> libc::c_int, + >, + ) -> *mut libc::c_void; +} +extern "C" { + pub fn calloc(arg1: usize, arg2: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn div(arg1: libc::c_int, arg2: libc::c_int) -> div_t; +} +extern "C" { + pub fn free(arg1: *mut libc::c_void); +} +extern "C" { + pub fn labs(arg1: libc::c_long) -> libc::c_long; +} +extern "C" { + pub fn ldiv(arg1: libc::c_long, arg2: libc::c_long) -> ldiv_t; +} +extern "C" { + pub fn malloc(arg1: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn memalign(arg1: usize, arg2: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn qsort( + arg1: *mut libc::c_void, + arg2: usize, + arg3: usize, + arg4: ::core::option::Option< + unsafe extern "C" fn( + arg1: *const libc::c_void, + arg2: *const libc::c_void, + ) -> libc::c_int, + >, + ); +} +extern "C" { + pub fn realloc(arg1: *mut libc::c_void, arg2: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn strtod(arg1: *const libc::c_char, arg2: *mut *mut libc::c_char) -> f64; +} +extern "C" { + pub fn strtol( + arg1: *const libc::c_char, + arg2: *mut *mut libc::c_char, + arg3: libc::c_int, + ) -> libc::c_long; +} +extern "C" { + pub fn strtof(arg1: *const libc::c_char, arg2: *mut *mut libc::c_char) -> f32; +} +extern "C" { + pub fn atoll(arg1: *const libc::c_char) -> libc::c_longlong; +} +extern "C" { + pub fn llabs(arg1: libc::c_longlong) -> libc::c_longlong; +} +extern "C" { + pub fn lldiv(arg1: libc::c_longlong, arg2: libc::c_longlong) -> lldiv_t; +} +extern "C" { + pub fn strtoll( + arg1: *const libc::c_char, + arg2: *mut *mut libc::c_char, + arg3: libc::c_int, + ) -> libc::c_longlong; +} +extern "C" { + pub fn strtoul( + arg1: *const libc::c_char, + arg2: *mut *mut libc::c_char, + arg3: libc::c_int, + ) -> libc::c_ulong; +} +extern "C" { + pub fn strtold(arg1: *const libc::c_char, arg2: *mut *mut libc::c_char) -> u128; +} +extern "C" { + pub fn strtoull( + arg1: *const libc::c_char, + arg2: *mut *mut libc::c_char, + arg3: libc::c_int, + ) -> libc::c_ulonglong; +} +extern "C" { + pub fn mblen(arg1: *const libc::c_char, arg2: usize) -> libc::c_int; +} +extern "C" { + pub fn mbstowcs(arg1: *mut wchar_t, arg2: *const libc::c_char, arg3: usize) -> usize; +} +extern "C" { + pub fn wctomb(arg1: *mut libc::c_char, arg2: wchar_t) -> libc::c_int; +} +extern "C" { + pub fn mbtowc(arg1: *mut wchar_t, arg2: *const libc::c_char, arg3: usize) -> libc::c_int; +} +extern "C" { + pub fn wcstombs(arg1: *mut libc::c_char, arg2: *const wchar_t, arg3: usize) -> usize; +} +extern "C" { + pub fn alloca(arg1: usize) -> *mut libc::c_void; +} +pub type int_least8_t = __int_least8_t; +pub type uint_least8_t = __uint_least8_t; +pub type int_least16_t = __int_least16_t; +pub type uint_least16_t = __uint_least16_t; +pub type int_least32_t = __int_least32_t; +pub type uint_least32_t = __uint_least32_t; +pub type int_least64_t = __int_least64_t; +pub type uint_least64_t = __uint_least64_t; +pub type int_fast8_t = __int_fast8_t; +pub type uint_fast8_t = __uint_fast8_t; +pub type int_fast16_t = __int_fast16_t; +pub type uint_fast16_t = __uint_fast16_t; +pub type int_fast32_t = __int_fast32_t; +pub type uint_fast32_t = __uint_fast32_t; +pub type int_fast64_t = __int_fast64_t; +pub type uint_fast64_t = __uint_fast64_t; +pub type intmax_t = __intmax_t; +pub type uintmax_t = __uintmax_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _attributes_t { + pub flags: u64, + pub xfrm: u64, +} +#[test] +fn bindgen_test_layout__attributes_t() { + assert_eq!( + ::core::mem::size_of::<_attributes_t>(), + 16usize, + concat!("Size of: ", stringify!(_attributes_t)) + ); + assert_eq!( + ::core::mem::align_of::<_attributes_t>(), + 8usize, + concat!("Alignment of ", stringify!(_attributes_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_attributes_t>())).flags as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_attributes_t), + "::", + stringify!(flags) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_attributes_t>())).xfrm as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_attributes_t), + "::", + stringify!(xfrm) + ) + ); +} +pub type sgx_attributes_t = _attributes_t; +pub type sgx_misc_select_t = u32; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_misc_attribute_t { + pub secs_attr: sgx_attributes_t, + pub misc_select: sgx_misc_select_t, +} +#[test] +fn bindgen_test_layout__sgx_misc_attribute_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_misc_attribute_t>(), + 24usize, + concat!("Size of: ", stringify!(_sgx_misc_attribute_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_misc_attribute_t>(), + 8usize, + concat!("Alignment of ", stringify!(_sgx_misc_attribute_t)) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<_sgx_misc_attribute_t>())).secs_attr as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_misc_attribute_t), + "::", + stringify!(secs_attr) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<_sgx_misc_attribute_t>())).misc_select as *const _ as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_sgx_misc_attribute_t), + "::", + stringify!(misc_select) + ) + ); +} +pub type sgx_misc_attribute_t = _sgx_misc_attribute_t; +pub type sgx_enclave_id_t = u64; +pub type sgx_key_128bit_t = [u8; 16usize]; +pub type sgx_isv_svn_t = u16; +pub type sgx_config_svn_t = u16; +pub type sgx_config_id_t = [u8; 64usize]; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_cpu_svn_t { + pub svn: [u8; 16usize], +} +#[test] +fn bindgen_test_layout__sgx_cpu_svn_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_cpu_svn_t>(), + 16usize, + concat!("Size of: ", stringify!(_sgx_cpu_svn_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_cpu_svn_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_cpu_svn_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_cpu_svn_t>())).svn as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_cpu_svn_t), + "::", + stringify!(svn) + ) + ); +} +pub type sgx_cpu_svn_t = _sgx_cpu_svn_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_key_id_t { + pub id: [u8; 32usize], +} +#[test] +fn bindgen_test_layout__sgx_key_id_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_key_id_t>(), + 32usize, + concat!("Size of: ", stringify!(_sgx_key_id_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_key_id_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_key_id_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_key_id_t>())).id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_key_id_t), + "::", + stringify!(id) + ) + ); +} +pub type sgx_key_id_t = _sgx_key_id_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _key_request_t { + pub key_name: u16, + pub key_policy: u16, + pub isv_svn: sgx_isv_svn_t, + pub reserved1: u16, + pub cpu_svn: sgx_cpu_svn_t, + pub attribute_mask: sgx_attributes_t, + pub key_id: sgx_key_id_t, + pub misc_mask: sgx_misc_select_t, + pub config_svn: sgx_config_svn_t, + pub reserved2: [u8; 434usize], +} +#[test] +fn bindgen_test_layout__key_request_t() { + assert_eq!( + ::core::mem::size_of::<_key_request_t>(), + 512usize, + concat!("Size of: ", stringify!(_key_request_t)) + ); + assert_eq!( + ::core::mem::align_of::<_key_request_t>(), + 8usize, + concat!("Alignment of ", stringify!(_key_request_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).key_name as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(key_name) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).key_policy as *const _ as usize }, + 2usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(key_policy) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).isv_svn as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(isv_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).reserved1 as *const _ as usize }, + 6usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(reserved1) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).cpu_svn as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(cpu_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).attribute_mask as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(attribute_mask) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).key_id as *const _ as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(key_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).misc_mask as *const _ as usize }, + 72usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(misc_mask) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).config_svn as *const _ as usize }, + 76usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(config_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_key_request_t>())).reserved2 as *const _ as usize }, + 78usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(reserved2) + ) + ); +} +impl Default for _key_request_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_key_request_t = _key_request_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_measurement_t { + pub m: [u8; 32usize], +} +#[test] +fn bindgen_test_layout__sgx_measurement_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_measurement_t>(), + 32usize, + concat!("Size of: ", stringify!(_sgx_measurement_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_measurement_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_measurement_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_measurement_t>())).m as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_measurement_t), + "::", + stringify!(m) + ) + ); +} +pub type sgx_measurement_t = _sgx_measurement_t; +pub type sgx_mac_t = [u8; 16usize]; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _sgx_report_data_t { + pub d: [u8; 64usize], +} +#[test] +fn bindgen_test_layout__sgx_report_data_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_report_data_t>(), + 64usize, + concat!("Size of: ", stringify!(_sgx_report_data_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_report_data_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_report_data_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_report_data_t>())).d as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_report_data_t), + "::", + stringify!(d) + ) + ); +} +impl Default for _sgx_report_data_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_report_data_t = _sgx_report_data_t; +pub type sgx_prod_id_t = u16; +pub type sgx_isvext_prod_id_t = [u8; 16usize]; +pub type sgx_isvfamily_id_t = [u8; 16usize]; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _target_info_t { + pub mr_enclave: sgx_measurement_t, + pub attributes: sgx_attributes_t, + pub reserved1: [u8; 2usize], + pub config_svn: sgx_config_svn_t, + pub misc_select: sgx_misc_select_t, + pub reserved2: [u8; 8usize], + pub config_id: sgx_config_id_t, + pub reserved3: [u8; 384usize], +} +#[test] +fn bindgen_test_layout__target_info_t() { + assert_eq!( + ::core::mem::size_of::<_target_info_t>(), + 512usize, + concat!("Size of: ", stringify!(_target_info_t)) + ); + assert_eq!( + ::core::mem::align_of::<_target_info_t>(), + 8usize, + concat!("Alignment of ", stringify!(_target_info_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).mr_enclave as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(mr_enclave) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).attributes as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(attributes) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).reserved1 as *const _ as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(reserved1) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).config_svn as *const _ as usize }, + 50usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(config_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).misc_select as *const _ as usize }, + 52usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(misc_select) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).reserved2 as *const _ as usize }, + 56usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(reserved2) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).config_id as *const _ as usize }, + 64usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(config_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_target_info_t>())).reserved3 as *const _ as usize }, + 128usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(reserved3) + ) + ); +} +impl Default for _target_info_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_target_info_t = _target_info_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _report_body_t { + pub cpu_svn: sgx_cpu_svn_t, + pub misc_select: sgx_misc_select_t, + pub reserved1: [u8; 12usize], + pub isv_ext_prod_id: sgx_isvext_prod_id_t, + pub attributes: sgx_attributes_t, + pub mr_enclave: sgx_measurement_t, + pub reserved2: [u8; 32usize], + pub mr_signer: sgx_measurement_t, + pub reserved3: [u8; 32usize], + pub config_id: sgx_config_id_t, + pub isv_prod_id: sgx_prod_id_t, + pub isv_svn: sgx_isv_svn_t, + pub config_svn: sgx_config_svn_t, + pub reserved4: [u8; 42usize], + pub isv_family_id: sgx_isvfamily_id_t, + pub report_data: sgx_report_data_t, +} +#[test] +fn bindgen_test_layout__report_body_t() { + assert_eq!( + ::core::mem::size_of::<_report_body_t>(), + 384usize, + concat!("Size of: ", stringify!(_report_body_t)) + ); + assert_eq!( + ::core::mem::align_of::<_report_body_t>(), + 8usize, + concat!("Alignment of ", stringify!(_report_body_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).cpu_svn as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(cpu_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).misc_select as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(misc_select) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).reserved1 as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved1) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).isv_ext_prod_id as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_ext_prod_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).attributes as *const _ as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(attributes) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).mr_enclave as *const _ as usize }, + 64usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(mr_enclave) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).reserved2 as *const _ as usize }, + 96usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved2) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).mr_signer as *const _ as usize }, + 128usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(mr_signer) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).reserved3 as *const _ as usize }, + 160usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved3) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).config_id as *const _ as usize }, + 192usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(config_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).isv_prod_id as *const _ as usize }, + 256usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_prod_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).isv_svn as *const _ as usize }, + 258usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).config_svn as *const _ as usize }, + 260usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(config_svn) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).reserved4 as *const _ as usize }, + 262usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved4) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).isv_family_id as *const _ as usize }, + 304usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_family_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_body_t>())).report_data as *const _ as usize }, + 320usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(report_data) + ) + ); +} +impl Default for _report_body_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_report_body_t = _report_body_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _report_t { + pub body: sgx_report_body_t, + pub key_id: sgx_key_id_t, + pub mac: sgx_mac_t, +} +#[test] +fn bindgen_test_layout__report_t() { + assert_eq!( + ::core::mem::size_of::<_report_t>(), + 432usize, + concat!("Size of: ", stringify!(_report_t)) + ); + assert_eq!( + ::core::mem::align_of::<_report_t>(), + 8usize, + concat!("Alignment of ", stringify!(_report_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_t>())).body as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_report_t), + "::", + stringify!(body) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_t>())).key_id as *const _ as usize }, + 384usize, + concat!( + "Offset of field: ", + stringify!(_report_t), + "::", + stringify!(key_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_report_t>())).mac as *const _ as usize }, + 416usize, + concat!( + "Offset of field: ", + stringify!(_report_t), + "::", + stringify!(mac) + ) + ); +} +impl Default for _report_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_report_t = _report_t; +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct _sgx_kss_config_t { + pub config_id: sgx_config_id_t, + pub config_svn: sgx_config_svn_t, +} +#[test] +fn bindgen_test_layout__sgx_kss_config_t() { + assert_eq!( + ::core::mem::size_of::<_sgx_kss_config_t>(), + 66usize, + concat!("Size of: ", stringify!(_sgx_kss_config_t)) + ); + assert_eq!( + ::core::mem::align_of::<_sgx_kss_config_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_kss_config_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_kss_config_t>())).config_id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_kss_config_t), + "::", + stringify!(config_id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_sgx_kss_config_t>())).config_svn as *const _ as usize }, + 64usize, + concat!( + "Offset of field: ", + stringify!(_sgx_kss_config_t), + "::", + stringify!(config_svn) + ) + ); +} +impl Default for _sgx_kss_config_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_kss_config_t = _sgx_kss_config_t; +pub type sgx_launch_token_t = [u8; 1024usize]; +extern "C" { + pub fn sgx_create_enclave( + file_name: *const libc::c_char, + debug: libc::c_int, + launch_token: *mut sgx_launch_token_t, + launch_token_updated: *mut libc::c_int, + enclave_id: *mut sgx_enclave_id_t, + misc_attr: *mut sgx_misc_attribute_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_create_enclave_ex( + file_name: *const libc::c_char, + debug: libc::c_int, + launch_token: *mut sgx_launch_token_t, + launch_token_updated: *mut libc::c_int, + enclave_id: *mut sgx_enclave_id_t, + misc_attr: *mut sgx_misc_attribute_t, + ex_features: u32, + ex_features_p: *mut *const libc::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_create_enclave_from_buffer_ex( + buffer: *mut u8, + buffer_size: usize, + debug: libc::c_int, + enclave_id: *mut sgx_enclave_id_t, + misc_attr: *mut sgx_misc_attribute_t, + ex_features: u32, + ex_features_p: *mut *const libc::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_create_encrypted_enclave( + file_name: *const libc::c_char, + debug: libc::c_int, + launch_token: *mut sgx_launch_token_t, + launch_token_updated: *mut libc::c_int, + enclave_id: *mut sgx_enclave_id_t, + misc_attr: *mut sgx_misc_attribute_t, + sealed_key: *mut u8, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_destroy_enclave(enclave_id: sgx_enclave_id_t) -> sgx_status_t; +} +extern "C" { + pub fn sgx_get_target_info( + enclave_id: sgx_enclave_id_t, + target_info: *mut sgx_target_info_t, + ) -> sgx_status_t; +} +pub type sgx_epid_group_id_t = [u8; 4usize]; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _spid_t { + pub id: [u8; 16usize], +} +#[test] +fn bindgen_test_layout__spid_t() { + assert_eq!( + ::core::mem::size_of::<_spid_t>(), + 16usize, + concat!("Size of: ", stringify!(_spid_t)) + ); + assert_eq!( + ::core::mem::align_of::<_spid_t>(), + 1usize, + concat!("Alignment of ", stringify!(_spid_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_spid_t>())).id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_spid_t), + "::", + stringify!(id) + ) + ); +} +pub type sgx_spid_t = _spid_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _basename_t { + pub name: [u8; 32usize], +} +#[test] +fn bindgen_test_layout__basename_t() { + assert_eq!( + ::core::mem::size_of::<_basename_t>(), + 32usize, + concat!("Size of: ", stringify!(_basename_t)) + ); + assert_eq!( + ::core::mem::align_of::<_basename_t>(), + 1usize, + concat!("Alignment of ", stringify!(_basename_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_basename_t>())).name as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_basename_t), + "::", + stringify!(name) + ) + ); +} +pub type sgx_basename_t = _basename_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _quote_nonce { + pub rand: [u8; 16usize], +} +#[test] +fn bindgen_test_layout__quote_nonce() { + assert_eq!( + ::core::mem::size_of::<_quote_nonce>(), + 16usize, + concat!("Size of: ", stringify!(_quote_nonce)) + ); + assert_eq!( + ::core::mem::align_of::<_quote_nonce>(), + 1usize, + concat!("Alignment of ", stringify!(_quote_nonce)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_quote_nonce>())).rand as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_quote_nonce), + "::", + stringify!(rand) + ) + ); +} +pub type sgx_quote_nonce_t = _quote_nonce; +pub const SGX_UNLINKABLE_SIGNATURE: sgx_quote_sign_type_t = 0; +pub const SGX_LINKABLE_SIGNATURE: sgx_quote_sign_type_t = 1; +pub type sgx_quote_sign_type_t = u32; +#[repr(C, packed)] +pub struct _quote_t { + pub version: u16, + pub sign_type: u16, + pub epid_group_id: sgx_epid_group_id_t, + pub qe_svn: sgx_isv_svn_t, + pub pce_svn: sgx_isv_svn_t, + pub xeid: u32, + pub basename: sgx_basename_t, + pub report_body: sgx_report_body_t, + pub signature_len: u32, + pub signature: __IncompleteArrayField, +} +#[test] +fn bindgen_test_layout__quote_t() { + assert_eq!( + ::core::mem::size_of::<_quote_t>(), + 436usize, + concat!("Size of: ", stringify!(_quote_t)) + ); + assert_eq!( + ::core::mem::align_of::<_quote_t>(), + 1usize, + concat!("Alignment of ", stringify!(_quote_t)) + ); +} +impl Default for _quote_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_quote_t = _quote_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _platform_info { + pub platform_info: [u8; 101usize], +} +#[test] +fn bindgen_test_layout__platform_info() { + assert_eq!( + ::core::mem::size_of::<_platform_info>(), + 101usize, + concat!("Size of: ", stringify!(_platform_info)) + ); + assert_eq!( + ::core::mem::align_of::<_platform_info>(), + 1usize, + concat!("Alignment of ", stringify!(_platform_info)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_platform_info>())).platform_info as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_platform_info), + "::", + stringify!(platform_info) + ) + ); +} +impl Default for _platform_info { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_platform_info_t = _platform_info; +#[repr(C, packed)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _update_info_bit { + pub ucodeUpdate: libc::c_int, + pub csmeFwUpdate: libc::c_int, + pub pswUpdate: libc::c_int, +} +#[test] +fn bindgen_test_layout__update_info_bit() { + assert_eq!( + ::core::mem::size_of::<_update_info_bit>(), + 12usize, + concat!("Size of: ", stringify!(_update_info_bit)) + ); + assert_eq!( + ::core::mem::align_of::<_update_info_bit>(), + 1usize, + concat!("Alignment of ", stringify!(_update_info_bit)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_update_info_bit>())).ucodeUpdate as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_update_info_bit), + "::", + stringify!(ucodeUpdate) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_update_info_bit>())).csmeFwUpdate as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(_update_info_bit), + "::", + stringify!(csmeFwUpdate) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_update_info_bit>())).pswUpdate as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_update_info_bit), + "::", + stringify!(pswUpdate) + ) + ); +} +pub type sgx_update_info_bit_t = _update_info_bit; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _att_key_id_t { + pub att_key_id: [u8; 256usize], +} +#[test] +fn bindgen_test_layout__att_key_id_t() { + assert_eq!( + ::core::mem::size_of::<_att_key_id_t>(), + 256usize, + concat!("Size of: ", stringify!(_att_key_id_t)) + ); + assert_eq!( + ::core::mem::align_of::<_att_key_id_t>(), + 1usize, + concat!("Alignment of ", stringify!(_att_key_id_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_att_key_id_t>())).att_key_id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_att_key_id_t), + "::", + stringify!(att_key_id) + ) + ); +} +impl Default for _att_key_id_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_att_key_id_t = _att_key_id_t; +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct _qe_report_info_t { + pub nonce: sgx_quote_nonce_t, + pub app_enclave_target_info: sgx_target_info_t, + pub qe_report: sgx_report_t, +} +#[test] +fn bindgen_test_layout__qe_report_info_t() { + assert_eq!( + ::core::mem::size_of::<_qe_report_info_t>(), + 960usize, + concat!("Size of: ", stringify!(_qe_report_info_t)) + ); + assert_eq!( + ::core::mem::align_of::<_qe_report_info_t>(), + 1usize, + concat!("Alignment of ", stringify!(_qe_report_info_t)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_qe_report_info_t>())).nonce as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_qe_report_info_t), + "::", + stringify!(nonce) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<_qe_report_info_t>())).app_enclave_target_info as *const _ + as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_qe_report_info_t), + "::", + stringify!(app_enclave_target_info) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<_qe_report_info_t>())).qe_report as *const _ as usize }, + 528usize, + concat!( + "Offset of field: ", + stringify!(_qe_report_info_t), + "::", + stringify!(qe_report) + ) + ); +} +impl Default for _qe_report_info_t { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgx_qe_report_info_t = _qe_report_info_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_aes_gcm_mac { + pub data: [u8; 16usize], +} +#[test] +fn bindgen_test_layout_sgxsd_aes_gcm_mac() { + assert_eq!( + ::core::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(sgxsd_aes_gcm_mac)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_aes_gcm_mac)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_aes_gcm_mac), + "::", + stringify!(data) + ) + ); +} +pub type sgxsd_aes_gcm_mac_t = sgxsd_aes_gcm_mac; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_aes_gcm_iv { + pub data: [u8; 12usize], +} +#[test] +fn bindgen_test_layout_sgxsd_aes_gcm_iv() { + assert_eq!( + ::core::mem::size_of::(), + 12usize, + concat!("Size of: ", stringify!(sgxsd_aes_gcm_iv)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_aes_gcm_iv)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_aes_gcm_iv), + "::", + stringify!(data) + ) + ); +} +pub type sgxsd_aes_gcm_iv_t = sgxsd_aes_gcm_iv; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_aes_gcm_key { + pub data: [u8; 32usize], +} +#[test] +fn bindgen_test_layout_sgxsd_aes_gcm_key() { + assert_eq!( + ::core::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(sgxsd_aes_gcm_key)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_aes_gcm_key)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_aes_gcm_key), + "::", + stringify!(data) + ) + ); +} +pub type sgxsd_aes_gcm_key_t = sgxsd_aes_gcm_key; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_curve25519_public_key { + pub x: [u8; 32usize], +} +#[test] +fn bindgen_test_layout_sgxsd_curve25519_public_key() { + assert_eq!( + ::core::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(sgxsd_curve25519_public_key)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_curve25519_public_key)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).x as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_curve25519_public_key), + "::", + stringify!(x) + ) + ); +} +pub type sgxsd_curve25519_public_key_t = sgxsd_curve25519_public_key; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_request_negotiation_request { + pub client_pubkey: sgxsd_curve25519_public_key_t, +} +#[test] +fn bindgen_test_layout_sgxsd_request_negotiation_request() { + assert_eq!( + ::core::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(sgxsd_request_negotiation_request)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!( + "Alignment of ", + stringify!(sgxsd_request_negotiation_request) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).client_pubkey as *const _ + as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_request), + "::", + stringify!(client_pubkey) + ) + ); +} +pub type sgxsd_request_negotiation_request_t = sgxsd_request_negotiation_request; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_pending_request_id { + pub data: [u8; 8usize], + pub iv: sgxsd_aes_gcm_iv_t, + pub mac: sgxsd_aes_gcm_mac_t, +} +#[test] +fn bindgen_test_layout_sgxsd_pending_request_id() { + assert_eq!( + ::core::mem::size_of::(), + 36usize, + concat!("Size of: ", stringify!(sgxsd_pending_request_id)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_pending_request_id)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_pending_request_id), + "::", + stringify!(data) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).iv as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_pending_request_id), + "::", + stringify!(iv) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).mac as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_pending_request_id), + "::", + stringify!(mac) + ) + ); +} +pub type sgxsd_pending_request_id_t = sgxsd_pending_request_id; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_request_negotiation_response { + pub server_static_pubkey: sgxsd_curve25519_public_key_t, + pub server_ephemeral_pubkey: sgxsd_curve25519_public_key_t, + pub encrypted_pending_request_id: sgxsd_request_negotiation_response__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_request_negotiation_response__bindgen_ty_1 { + pub data: [u8; 36usize], + pub iv: sgxsd_aes_gcm_iv_t, + pub mac: sgxsd_aes_gcm_mac_t, +} +#[test] +fn bindgen_test_layout_sgxsd_request_negotiation_response__bindgen_ty_1() { + assert_eq!( + ::core::mem::size_of::(), + 64usize, + concat!( + "Size of: ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1) + ) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!( + "Alignment of ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).data + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1), + "::", + stringify!(data) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).iv + as *const _ as usize + }, + 36usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1), + "::", + stringify!(iv) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).mac + as *const _ as usize + }, + 48usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1), + "::", + stringify!(mac) + ) + ); +} +impl Default for sgxsd_request_negotiation_response__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout_sgxsd_request_negotiation_response() { + assert_eq!( + ::core::mem::size_of::(), + 128usize, + concat!("Size of: ", stringify!(sgxsd_request_negotiation_response)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!( + "Alignment of ", + stringify!(sgxsd_request_negotiation_response) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).server_static_pubkey + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response), + "::", + stringify!(server_static_pubkey) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).server_ephemeral_pubkey + as *const _ as usize + }, + 32usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response), + "::", + stringify!(server_ephemeral_pubkey) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())) + .encrypted_pending_request_id as *const _ as usize + }, + 64usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response), + "::", + stringify!(encrypted_pending_request_id) + ) + ); +} +impl Default for sgxsd_request_negotiation_response { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgxsd_request_negotiation_response_t = sgxsd_request_negotiation_response; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_msg_tag { + pub __bindgen_anon_1: sgxsd_msg_tag__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union sgxsd_msg_tag__bindgen_ty_1 { + pub p_tag: *mut libc::c_void, + pub tag: u64, + _bindgen_union_align: u64, +} +#[test] +fn bindgen_test_layout_sgxsd_msg_tag__bindgen_ty_1() { + assert_eq!( + ::core::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(sgxsd_msg_tag__bindgen_ty_1)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_msg_tag__bindgen_ty_1)) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).p_tag as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_tag__bindgen_ty_1), + "::", + stringify!(p_tag) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).tag as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_tag__bindgen_ty_1), + "::", + stringify!(tag) + ) + ); +} +impl Default for sgxsd_msg_tag__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout_sgxsd_msg_tag() { + assert_eq!( + ::core::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(sgxsd_msg_tag)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_msg_tag)) + ); +} +impl Default for sgxsd_msg_tag { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgxsd_msg_tag_t = sgxsd_msg_tag; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_msg_header { + pub iv: sgxsd_aes_gcm_iv_t, + pub mac: sgxsd_aes_gcm_mac_t, + pub pending_request_id: sgxsd_pending_request_id_t, +} +#[test] +fn bindgen_test_layout_sgxsd_msg_header() { + assert_eq!( + ::core::mem::size_of::(), + 64usize, + concat!("Size of: ", stringify!(sgxsd_msg_header)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_msg_header)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).iv as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_header), + "::", + stringify!(iv) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).mac as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_header), + "::", + stringify!(mac) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).pending_request_id as *const _ as usize + }, + 28usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_header), + "::", + stringify!(pending_request_id) + ) + ); +} +pub type sgxsd_msg_header_t = sgxsd_msg_header; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_node_init_args { + pub pending_requests_table_order: u8, +} +#[test] +fn bindgen_test_layout_sgxsd_node_init_args() { + assert_eq!( + ::core::mem::size_of::(), + 1usize, + concat!("Size of: ", stringify!(sgxsd_node_init_args)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_node_init_args)) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).pending_requests_table_order + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_node_init_args), + "::", + stringify!(pending_requests_table_order) + ) + ); +} +pub type sgxsd_node_init_args_t = sgxsd_node_init_args; +pub type sgxsd_server_state_handle_t = u64; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_status { + pub ok: bool, + pub name: *const libc::c_char, + pub code: i64, +} +#[test] +fn bindgen_test_layout_sgxsd_status() { + assert_eq!( + ::core::mem::size_of::(), + 24usize, + concat!("Size of: ", stringify!(sgxsd_status)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_status)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).ok as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_status), + "::", + stringify!(ok) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).name as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_status), + "::", + stringify!(name) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).code as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_status), + "::", + stringify!(code) + ) + ); +} +impl Default for sgxsd_status { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgxsd_status_t = sgxsd_status; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_enclave { + pub id: sgx_enclave_id_t, + pub __bindgen_anon_1: sgxsd_enclave__bindgen_ty_1, + pub launch_token: sgx_launch_token_t, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union sgxsd_enclave__bindgen_ty_1 { + pub gid: sgx_epid_group_id_t, + pub gid32: u32, + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout_sgxsd_enclave__bindgen_ty_1() { + assert_eq!( + ::core::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(sgxsd_enclave__bindgen_ty_1)) + ); + assert_eq!( + ::core::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(sgxsd_enclave__bindgen_ty_1)) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).gid as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_enclave__bindgen_ty_1), + "::", + stringify!(gid) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).gid32 as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_enclave__bindgen_ty_1), + "::", + stringify!(gid32) + ) + ); +} +impl Default for sgxsd_enclave__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout_sgxsd_enclave() { + assert_eq!( + ::core::mem::size_of::(), + 1040usize, + concat!("Size of: ", stringify!(sgxsd_enclave)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_enclave)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_enclave), + "::", + stringify!(id) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).launch_token as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_enclave), + "::", + stringify!(launch_token) + ) + ); +} +impl Default for sgxsd_enclave { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgxsd_enclave_t = sgxsd_enclave; +pub type sgxsd_start_callback_t = ::core::option::Option< + unsafe extern "C" fn(arg1: sgxsd_enclave_t, arg2: *mut __va_list_tag) -> sgxsd_status_t, +>; +extern "C" { + pub fn sgxsd_start( + enclave_path: *const libc::c_char, + debug: bool, + p_launch_token: *const sgx_launch_token_t, + p_node_init_args: *const sgxsd_node_init_args_t, + p_callback: sgxsd_start_callback_t, + ... + ) -> sgxsd_status_t; +} +extern "C" { + pub fn sgxsd_get_next_quote( + enclave_id: sgx_enclave_id_t, + spid: sgx_spid_t, + p_sig_rl: *const u8, + sig_rl_size: u32, + p_quote: *mut sgx_quote_t, + quote_size: u32, + ) -> sgxsd_status_t; +} +pub const SGXSD_ERROR_PENDING_REQUEST_NOT_FOUND: sgxsd_status_code = 65537; +pub type sgxsd_status_code = u32; +pub use self::sgxsd_status_code as sgxsd_status_code_t; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_msg_buf { + pub data: *mut u8, + pub size: u32, +} +#[test] +fn bindgen_test_layout_sgxsd_msg_buf() { + assert_eq!( + ::core::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(sgxsd_msg_buf)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_msg_buf)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_buf), + "::", + stringify!(data) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).size as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_buf), + "::", + stringify!(size) + ) + ); +} +impl Default for sgxsd_msg_buf { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgxsd_msg_buf_t = sgxsd_msg_buf; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_msg_from { + pub valid: bool, + pub tag: sgxsd_msg_tag_t, + pub server_key: sgxsd_aes_gcm_key_t, +} +#[test] +fn bindgen_test_layout_sgxsd_msg_from() { + assert_eq!( + ::core::mem::size_of::(), + 48usize, + concat!("Size of: ", stringify!(sgxsd_msg_from)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_msg_from)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).valid as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_from), + "::", + stringify!(valid) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).tag as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_from), + "::", + stringify!(tag) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).server_key as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_from), + "::", + stringify!(server_key) + ) + ); +} +impl Default for sgxsd_msg_from { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +pub type sgxsd_msg_from_t = sgxsd_msg_from; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct sgxsd_server_state { + _unused: [u8; 0], +} +pub type sgxsd_server_state_t = sgxsd_server_state; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct sgxsd_server_init_args { + _unused: [u8; 0], +} +pub type sgxsd_server_init_args_t = sgxsd_server_init_args; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct sgxsd_server_handle_call_args { + _unused: [u8; 0], +} +pub type sgxsd_server_handle_call_args_t = sgxsd_server_handle_call_args; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct sgxsd_server_terminate_args { + _unused: [u8; 0], +} +pub type sgxsd_server_terminate_args_t = sgxsd_server_terminate_args; +extern "C" { + pub fn sgxsd_enclave_server_init( + p_args: *const sgxsd_server_init_args_t, + pp_state: *mut *mut sgxsd_server_state_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_server_handle_call( + p_args: *const sgxsd_server_handle_call_args_t, + msg: sgxsd_msg_buf_t, + from: sgxsd_msg_from_t, + pp_state: *mut *mut sgxsd_server_state_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_server_terminate( + p_args: *const sgxsd_server_terminate_args_t, + p_state: *mut sgxsd_server_state_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_server_reply( + reply_buf: sgxsd_msg_buf_t, + p_from: *mut sgxsd_msg_from_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_server_noreply(p_from: *mut sgxsd_msg_from_t) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_aes_gcm_encrypt( + p_key: *const sgxsd_aes_gcm_key_t, + p_src: *const libc::c_void, + src_len: u32, + p_dst: *mut libc::c_void, + p_iv: *const sgxsd_aes_gcm_iv_t, + p_aad: *const libc::c_void, + aad_len: u32, + p_out_mac: *mut sgxsd_aes_gcm_mac_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_aes_gcm_decrypt( + p_key: *const sgxsd_aes_gcm_key_t, + p_src: *const libc::c_void, + src_len: u32, + p_dst: *mut libc::c_void, + p_iv: *const sgxsd_aes_gcm_iv_t, + p_aad: *const libc::c_void, + aad_len: u32, + p_in_mac: *const sgxsd_aes_gcm_mac_t, + ) -> sgx_status_t; +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_rand_buf { + pub x: [u8; 32usize], +} +#[test] +fn bindgen_test_layout_sgxsd_rand_buf() { + assert_eq!( + ::core::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(sgxsd_rand_buf)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_rand_buf)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).x as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_rand_buf), + "::", + stringify!(x) + ) + ); +} +pub type sgxsd_rand_buf_t = sgxsd_rand_buf; +pub type sgxsd_curve25519_private_key_t = sgxsd_rand_buf; +extern "C" { + pub fn sgxsd_enclave_read_rand(p_privkey: *mut sgxsd_rand_buf_t) -> sgx_status_t; +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_curve25519_key_pair { + pub privkey: sgxsd_curve25519_private_key_t, + pub pubkey: sgxsd_curve25519_public_key_t, +} +#[test] +fn bindgen_test_layout_sgxsd_curve25519_key_pair() { + assert_eq!( + ::core::mem::size_of::(), + 64usize, + concat!("Size of: ", stringify!(sgxsd_curve25519_key_pair)) + ); + assert_eq!( + ::core::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_curve25519_key_pair)) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).privkey as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_curve25519_key_pair), + "::", + stringify!(privkey) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::())).pubkey as *const _ as usize + }, + 32usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_curve25519_key_pair), + "::", + stringify!(pubkey) + ) + ); +} +pub type sgxsd_curve25519_key_pair_t = sgxsd_curve25519_key_pair; +pub type errno_t = libc::c_int; +extern "C" { + pub fn memchr(arg1: *const libc::c_void, arg2: libc::c_int, arg3: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn memcmp(arg1: *const libc::c_void, arg2: *const libc::c_void, arg3: usize) + -> libc::c_int; +} +extern "C" { + pub fn memcpy( + arg1: *mut libc::c_void, + arg2: *const libc::c_void, + arg3: usize, + ) -> *mut libc::c_void; +} +extern "C" { + pub fn memmove( + arg1: *mut libc::c_void, + arg2: *const libc::c_void, + arg3: usize, + ) -> *mut libc::c_void; +} +extern "C" { + pub fn memset(arg1: *mut libc::c_void, arg2: libc::c_int, arg3: usize) -> *mut libc::c_void; +} +extern "C" { + pub fn strchr(arg1: *const libc::c_char, arg2: libc::c_int) -> *mut libc::c_char; +} +extern "C" { + pub fn strcmp(arg1: *const libc::c_char, arg2: *const libc::c_char) -> libc::c_int; +} +extern "C" { + pub fn strcoll(arg1: *const libc::c_char, arg2: *const libc::c_char) -> libc::c_int; +} +extern "C" { + pub fn strcspn(arg1: *const libc::c_char, arg2: *const libc::c_char) -> usize; +} +extern "C" { + pub fn strerror(arg1: libc::c_int) -> *mut libc::c_char; +} +extern "C" { + pub fn strlen(arg1: *const libc::c_char) -> usize; +} +extern "C" { + pub fn strncat( + arg1: *mut libc::c_char, + arg2: *const libc::c_char, + arg3: usize, + ) -> *mut libc::c_char; +} +extern "C" { + pub fn strncmp( + arg1: *const libc::c_char, + arg2: *const libc::c_char, + arg3: usize, + ) -> libc::c_int; +} +extern "C" { + pub fn strncpy( + arg1: *mut libc::c_char, + arg2: *const libc::c_char, + arg3: usize, + ) -> *mut libc::c_char; +} +extern "C" { + pub fn strpbrk(arg1: *const libc::c_char, arg2: *const libc::c_char) -> *mut libc::c_char; +} +extern "C" { + pub fn strrchr(arg1: *const libc::c_char, arg2: libc::c_int) -> *mut libc::c_char; +} +extern "C" { + pub fn strspn(arg1: *const libc::c_char, arg2: *const libc::c_char) -> usize; +} +extern "C" { + pub fn strstr(arg1: *const libc::c_char, arg2: *const libc::c_char) -> *mut libc::c_char; +} +extern "C" { + pub fn strtok(arg1: *mut libc::c_char, arg2: *const libc::c_char) -> *mut libc::c_char; +} +extern "C" { + pub fn strxfrm(arg1: *mut libc::c_char, arg2: *const libc::c_char, arg3: usize) -> usize; +} +extern "C" { + pub fn strlcpy(arg1: *mut libc::c_char, arg2: *const libc::c_char, arg3: usize) -> usize; +} +extern "C" { + pub fn memset_s(s: *mut libc::c_void, smax: usize, c: libc::c_int, n: usize) -> errno_t; +} +extern "C" { + pub fn strndup(arg1: *const libc::c_char, arg2: usize) -> *mut libc::c_char; +} +extern "C" { + pub fn strnlen(arg1: *const libc::c_char, arg2: usize) -> usize; +} +extern "C" { + pub fn consttime_memequal( + b1: *const libc::c_void, + b2: *const libc::c_void, + len: usize, + ) -> libc::c_int; +} +extern "C" { + pub fn bcmp(arg1: *const libc::c_void, arg2: *const libc::c_void, arg3: usize) -> libc::c_int; +} +extern "C" { + pub fn bcopy(arg1: *const libc::c_void, arg2: *mut libc::c_void, arg3: usize); +} +extern "C" { + pub fn bzero(arg1: *mut libc::c_void, arg2: usize); +} +extern "C" { + pub fn index(arg1: *const libc::c_char, arg2: libc::c_int) -> *mut libc::c_char; +} +extern "C" { + pub fn mempcpy( + arg1: *mut libc::c_void, + arg2: *const libc::c_void, + arg3: usize, + ) -> *mut libc::c_void; +} +extern "C" { + pub fn rindex(arg1: *const libc::c_char, arg2: libc::c_int) -> *mut libc::c_char; +} +extern "C" { + pub fn stpncpy( + dest: *mut libc::c_char, + src: *const libc::c_char, + n: usize, + ) -> *mut libc::c_char; +} +extern "C" { + pub fn strcasecmp(arg1: *const libc::c_char, arg2: *const libc::c_char) -> libc::c_int; +} +extern "C" { + pub fn strncasecmp( + arg1: *const libc::c_char, + arg2: *const libc::c_char, + arg3: usize, + ) -> libc::c_int; +} +extern "C" { + pub fn ffs(arg1: libc::c_int) -> libc::c_int; +} +extern "C" { + pub fn ffsl(arg1: libc::c_long) -> libc::c_int; +} +extern "C" { + pub fn ffsll(arg1: libc::c_longlong) -> libc::c_int; +} +extern "C" { + pub fn strtok_r( + arg1: *mut libc::c_char, + arg2: *const libc::c_char, + arg3: *mut *mut libc::c_char, + ) -> *mut libc::c_char; +} +extern "C" { + pub fn strerror_r(arg1: libc::c_int, arg2: *mut libc::c_char, arg3: usize) -> libc::c_int; +} +#[doc = " \\brief Class type for hash function implementations."] +#[doc = ""] +#[doc = " A `br_hash_class` instance references the methods implementing a hash"] +#[doc = " function. Constant instances of this structure are defined for each"] +#[doc = " implemented hash function. Such instances are also called \"vtables\"."] +#[doc = ""] +#[doc = " Vtables are used to support object-oriented programming, as"] +#[doc = " described on [the BearSSL Web site](https://www.bearssl.org/oop.html)."] +pub type br_hash_class = br_hash_class_; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct br_hash_class_ { + #[doc = " \\brief Size (in bytes) of the context structure appropriate for"] + #[doc = " computing this hash function."] + pub context_size: usize, + #[doc = " \\brief Descriptor word that contains information about the hash"] + #[doc = " function."] + #[doc = ""] + #[doc = " For each word `xxx` described below, use `BR_HASHDESC_xxx_OFF`"] + #[doc = " and `BR_HASHDESC_xxx_MASK` to access the specific value, as"] + #[doc = " follows:"] + #[doc = ""] + #[doc = " (hf->desc >> BR_HASHDESC_xxx_OFF) & BR_HASHDESC_xxx_MASK"] + #[doc = ""] + #[doc = " The defined elements are:"] + #[doc = ""] + #[doc = " - `ID`: the symbolic identifier for the function, as defined"] + #[doc = " in [TLS](https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1)"] + #[doc = " (MD5 = 1, SHA-1 = 2,...)."] + #[doc = ""] + #[doc = " - `OUT`: hash output size, in bytes."] + #[doc = ""] + #[doc = " - `STATE`: internal running state size, in bytes."] + #[doc = ""] + #[doc = " - `LBLEN`: base-2 logarithm for the internal block size, as"] + #[doc = " defined for HMAC processing (this is 6 for MD5, SHA-1, SHA-224"] + #[doc = " and SHA-256, since these functions use 64-byte blocks; for"] + #[doc = " SHA-384 and SHA-512, this is 7, corresponding to their"] + #[doc = " 128-byte blocks)."] + #[doc = ""] + #[doc = " The descriptor may contain a few other flags."] + pub desc: u32, + #[doc = " \\brief Initialisation method."] + #[doc = ""] + #[doc = " This method takes as parameter a pointer to a context area,"] + #[doc = " that it initialises. The first field of the context is set"] + #[doc = " to this vtable; other elements are initialised for a new hash"] + #[doc = " computation."] + #[doc = ""] + #[doc = " \\param ctx pointer to (the first field of) the context."] + pub init: ::core::option::Option, + #[doc = " \\brief Data injection method."] + #[doc = ""] + #[doc = " The `len` bytes starting at address `data` are injected into"] + #[doc = " the running hash computation incarnated by the specified"] + #[doc = " context. The context is updated accordingly. It is allowed"] + #[doc = " to have `len == 0`, in which case `data` is ignored (and could"] + #[doc = " be `NULL`), and nothing happens."] + #[doc = " on the input data."] + #[doc = ""] + #[doc = " \\param ctx pointer to (the first field of) the context."] + #[doc = " \\param data pointer to the first data byte to inject."] + #[doc = " \\param len number of bytes to inject."] + pub update: ::core::option::Option< + unsafe extern "C" fn(ctx: *mut *const br_hash_class, data: *const libc::c_void, len: usize), + >, + #[doc = " \\brief Produce hash output."] + #[doc = ""] + #[doc = " The hash output corresponding to all data bytes injected in the"] + #[doc = " context since the last `init()` call is computed, and written"] + #[doc = " in the buffer pointed to by `dst`. The hash output size depends"] + #[doc = " on the implemented hash function (e.g. 16 bytes for MD5)."] + #[doc = " The context is _not_ modified by this call, so further bytes"] + #[doc = " may be afterwards injected to continue the current computation."] + #[doc = ""] + #[doc = " \\param ctx pointer to (the first field of) the context."] + #[doc = " \\param dst destination buffer for the hash output."] + pub out: ::core::option::Option< + unsafe extern "C" fn(ctx: *const *const br_hash_class, dst: *mut libc::c_void), + >, + #[doc = " \\brief Get running state."] + #[doc = ""] + #[doc = " This method saves the current running state into the `dst`"] + #[doc = " buffer. What constitutes the \"running state\" depends on the"] + #[doc = " hash function; for Merkle-Damg\u{e5}rd hash functions (like"] + #[doc = " MD5 or SHA-1), this is the output obtained after processing"] + #[doc = " each block. The number of bytes injected so far is returned."] + #[doc = " The context is not modified by this call."] + #[doc = ""] + #[doc = " \\param ctx pointer to (the first field of) the context."] + #[doc = " \\param dst destination buffer for the state."] + #[doc = " \\return the injected total byte length."] + pub state: ::core::option::Option< + unsafe extern "C" fn(ctx: *const *const br_hash_class, dst: *mut libc::c_void) -> u64, + >, + #[doc = " \\brief Set running state."] + #[doc = ""] + #[doc = " This methods replaces the running state for the function."] + #[doc = ""] + #[doc = " \\param ctx pointer to (the first field of) the context."] + #[doc = " \\param stb source buffer for the state."] + #[doc = " \\param count injected total byte length."] + pub set_state: ::core::option::Option< + unsafe extern "C" fn(ctx: *mut *const br_hash_class, stb: *const libc::c_void, count: u64), + >, +} +#[test] +fn bindgen_test_layout_br_hash_class_() { + assert_eq!( + ::core::mem::size_of::(), + 56usize, + concat!("Size of: ", stringify!(br_hash_class_)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(br_hash_class_)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).context_size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_hash_class_), + "::", + stringify!(context_size) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).desc as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(br_hash_class_), + "::", + stringify!(desc) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).init as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(br_hash_class_), + "::", + stringify!(init) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).update as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(br_hash_class_), + "::", + stringify!(update) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).out as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(br_hash_class_), + "::", + stringify!(out) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).state as *const _ as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(br_hash_class_), + "::", + stringify!(state) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).set_state as *const _ as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(br_hash_class_), + "::", + stringify!(set_state) + ) + ); +} +extern "C" { + pub static br_md5_vtable: br_hash_class; +} +#[doc = " \\brief MD5 context."] +#[doc = ""] +#[doc = " First field is a pointer to the vtable; it is set by the initialisation"] +#[doc = " function. Other fields are not supposed to be accessed by user code."] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct br_md5_context { + #[doc = " \\brief Pointer to vtable for this context."] + pub vtable: *const br_hash_class, + pub buf: [libc::c_uchar; 64usize], + pub count: u64, + pub val: [u32; 4usize], +} +#[test] +fn bindgen_test_layout_br_md5_context() { + assert_eq!( + ::core::mem::size_of::(), + 96usize, + concat!("Size of: ", stringify!(br_md5_context)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(br_md5_context)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).vtable as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_md5_context), + "::", + stringify!(vtable) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).buf as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(br_md5_context), + "::", + stringify!(buf) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).count as *const _ as usize }, + 72usize, + concat!( + "Offset of field: ", + stringify!(br_md5_context), + "::", + stringify!(count) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).val as *const _ as usize }, + 80usize, + concat!( + "Offset of field: ", + stringify!(br_md5_context), + "::", + stringify!(val) + ) + ); +} +impl Default for br_md5_context { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +extern "C" { + #[doc = " \\brief MD5 context initialisation."] + #[doc = ""] + #[doc = " This function initialises or resets a context for a new MD5"] + #[doc = " computation. It also sets the vtable pointer."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + pub fn br_md5_init(ctx: *mut br_md5_context); +} +extern "C" { + #[doc = " \\brief Inject some data bytes in a running MD5 computation."] + #[doc = ""] + #[doc = " The provided context is updated with some data bytes. If the number"] + #[doc = " of bytes (`len`) is zero, then the data pointer (`data`) is ignored"] + #[doc = " and may be `NULL`, and this function does nothing."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param data pointer to the injected data."] + #[doc = " \\param len injected data length (in bytes)."] + pub fn br_md5_update(ctx: *mut br_md5_context, data: *const libc::c_void, len: usize); +} +extern "C" { + #[doc = " \\brief Compute MD5 output."] + #[doc = ""] + #[doc = " The MD5 output for the concatenation of all bytes injected in the"] + #[doc = " provided context since the last initialisation or reset call, is"] + #[doc = " computed and written in the buffer pointed to by `out`. The context"] + #[doc = " itself is not modified, so extra bytes may be injected afterwards"] + #[doc = " to continue that computation."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param out destination buffer for the hash output."] + pub fn br_md5_out(ctx: *const br_md5_context, out: *mut libc::c_void); +} +extern "C" { + #[doc = " \\brief Save MD5 running state."] + #[doc = ""] + #[doc = " The running state for MD5 (output of the last internal block"] + #[doc = " processing) is written in the buffer pointed to by `out`. The"] + #[doc = " number of bytes injected since the last initialisation or reset"] + #[doc = " call is returned. The context is not modified."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param out destination buffer for the running state."] + #[doc = " \\return the injected total byte length."] + pub fn br_md5_state(ctx: *const br_md5_context, out: *mut libc::c_void) -> u64; +} +extern "C" { + #[doc = " \\brief Restore MD5 running state."] + #[doc = ""] + #[doc = " The running state for MD5 is set to the provided values."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param stb source buffer for the running state."] + #[doc = " \\param count the injected total byte length."] + pub fn br_md5_set_state(ctx: *mut br_md5_context, stb: *const libc::c_void, count: u64); +} +extern "C" { + pub static br_sha1_vtable: br_hash_class; +} +#[doc = " \\brief SHA-1 context."] +#[doc = ""] +#[doc = " First field is a pointer to the vtable; it is set by the initialisation"] +#[doc = " function. Other fields are not supposed to be accessed by user code."] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct br_sha1_context { + #[doc = " \\brief Pointer to vtable for this context."] + pub vtable: *const br_hash_class, + pub buf: [libc::c_uchar; 64usize], + pub count: u64, + pub val: [u32; 5usize], +} +#[test] +fn bindgen_test_layout_br_sha1_context() { + assert_eq!( + ::core::mem::size_of::(), + 104usize, + concat!("Size of: ", stringify!(br_sha1_context)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(br_sha1_context)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).vtable as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_sha1_context), + "::", + stringify!(vtable) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).buf as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(br_sha1_context), + "::", + stringify!(buf) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).count as *const _ as usize }, + 72usize, + concat!( + "Offset of field: ", + stringify!(br_sha1_context), + "::", + stringify!(count) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).val as *const _ as usize }, + 80usize, + concat!( + "Offset of field: ", + stringify!(br_sha1_context), + "::", + stringify!(val) + ) + ); +} +impl Default for br_sha1_context { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +extern "C" { + #[doc = " \\brief SHA-1 context initialisation."] + #[doc = ""] + #[doc = " This function initialises or resets a context for a new SHA-1"] + #[doc = " computation. It also sets the vtable pointer."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + pub fn br_sha1_init(ctx: *mut br_sha1_context); +} +extern "C" { + #[doc = " \\brief Inject some data bytes in a running SHA-1 computation."] + #[doc = ""] + #[doc = " The provided context is updated with some data bytes. If the number"] + #[doc = " of bytes (`len`) is zero, then the data pointer (`data`) is ignored"] + #[doc = " and may be `NULL`, and this function does nothing."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param data pointer to the injected data."] + #[doc = " \\param len injected data length (in bytes)."] + pub fn br_sha1_update(ctx: *mut br_sha1_context, data: *const libc::c_void, len: usize); +} +extern "C" { + #[doc = " \\brief Compute SHA-1 output."] + #[doc = ""] + #[doc = " The SHA-1 output for the concatenation of all bytes injected in the"] + #[doc = " provided context since the last initialisation or reset call, is"] + #[doc = " computed and written in the buffer pointed to by `out`. The context"] + #[doc = " itself is not modified, so extra bytes may be injected afterwards"] + #[doc = " to continue that computation."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param out destination buffer for the hash output."] + pub fn br_sha1_out(ctx: *const br_sha1_context, out: *mut libc::c_void); +} +extern "C" { + #[doc = " \\brief Save SHA-1 running state."] + #[doc = ""] + #[doc = " The running state for SHA-1 (output of the last internal block"] + #[doc = " processing) is written in the buffer pointed to by `out`. The"] + #[doc = " number of bytes injected since the last initialisation or reset"] + #[doc = " call is returned. The context is not modified."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param out destination buffer for the running state."] + #[doc = " \\return the injected total byte length."] + pub fn br_sha1_state(ctx: *const br_sha1_context, out: *mut libc::c_void) -> u64; +} +extern "C" { + #[doc = " \\brief Restore SHA-1 running state."] + #[doc = ""] + #[doc = " The running state for SHA-1 is set to the provided values."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param stb source buffer for the running state."] + #[doc = " \\param count the injected total byte length."] + pub fn br_sha1_set_state(ctx: *mut br_sha1_context, stb: *const libc::c_void, count: u64); +} +extern "C" { + pub static br_sha224_vtable: br_hash_class; +} +#[doc = " \\brief SHA-224 context."] +#[doc = ""] +#[doc = " First field is a pointer to the vtable; it is set by the initialisation"] +#[doc = " function. Other fields are not supposed to be accessed by user code."] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct br_sha224_context { + #[doc = " \\brief Pointer to vtable for this context."] + pub vtable: *const br_hash_class, + pub buf: [libc::c_uchar; 64usize], + pub count: u64, + pub val: [u32; 8usize], +} +#[test] +fn bindgen_test_layout_br_sha224_context() { + assert_eq!( + ::core::mem::size_of::(), + 112usize, + concat!("Size of: ", stringify!(br_sha224_context)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(br_sha224_context)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).vtable as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_sha224_context), + "::", + stringify!(vtable) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).buf as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(br_sha224_context), + "::", + stringify!(buf) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).count as *const _ as usize }, + 72usize, + concat!( + "Offset of field: ", + stringify!(br_sha224_context), + "::", + stringify!(count) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).val as *const _ as usize }, + 80usize, + concat!( + "Offset of field: ", + stringify!(br_sha224_context), + "::", + stringify!(val) + ) + ); +} +impl Default for br_sha224_context { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +extern "C" { + #[doc = " \\brief SHA-224 context initialisation."] + #[doc = ""] + #[doc = " This function initialises or resets a context for a new SHA-224"] + #[doc = " computation. It also sets the vtable pointer."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + pub fn br_sha224_init(ctx: *mut br_sha224_context); +} +extern "C" { + #[doc = " \\brief Inject some data bytes in a running SHA-224 computation."] + #[doc = ""] + #[doc = " The provided context is updated with some data bytes. If the number"] + #[doc = " of bytes (`len`) is zero, then the data pointer (`data`) is ignored"] + #[doc = " and may be `NULL`, and this function does nothing."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param data pointer to the injected data."] + #[doc = " \\param len injected data length (in bytes)."] + pub fn br_sha224_update(ctx: *mut br_sha224_context, data: *const libc::c_void, len: usize); +} +extern "C" { + #[doc = " \\brief Compute SHA-224 output."] + #[doc = ""] + #[doc = " The SHA-224 output for the concatenation of all bytes injected in the"] + #[doc = " provided context since the last initialisation or reset call, is"] + #[doc = " computed and written in the buffer pointed to by `out`. The context"] + #[doc = " itself is not modified, so extra bytes may be injected afterwards"] + #[doc = " to continue that computation."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param out destination buffer for the hash output."] + pub fn br_sha224_out(ctx: *const br_sha224_context, out: *mut libc::c_void); +} +extern "C" { + #[doc = " \\brief Save SHA-224 running state."] + #[doc = ""] + #[doc = " The running state for SHA-224 (output of the last internal block"] + #[doc = " processing) is written in the buffer pointed to by `out`. The"] + #[doc = " number of bytes injected since the last initialisation or reset"] + #[doc = " call is returned. The context is not modified."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param out destination buffer for the running state."] + #[doc = " \\return the injected total byte length."] + pub fn br_sha224_state(ctx: *const br_sha224_context, out: *mut libc::c_void) -> u64; +} +extern "C" { + #[doc = " \\brief Restore SHA-224 running state."] + #[doc = ""] + #[doc = " The running state for SHA-224 is set to the provided values."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param stb source buffer for the running state."] + #[doc = " \\param count the injected total byte length."] + pub fn br_sha224_set_state(ctx: *mut br_sha224_context, stb: *const libc::c_void, count: u64); +} +extern "C" { + pub static br_sha256_vtable: br_hash_class; +} +pub type br_sha256_context = br_sha224_context; +extern "C" { + #[doc = " \\brief SHA-256 context initialisation."] + #[doc = ""] + #[doc = " This function initialises or resets a context for a new SHA-256"] + #[doc = " computation. It also sets the vtable pointer."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + pub fn br_sha256_init(ctx: *mut br_sha256_context); +} +extern "C" { + #[doc = " \\brief Compute SHA-256 output."] + #[doc = ""] + #[doc = " The SHA-256 output for the concatenation of all bytes injected in the"] + #[doc = " provided context since the last initialisation or reset call, is"] + #[doc = " computed and written in the buffer pointed to by `out`. The context"] + #[doc = " itself is not modified, so extra bytes may be injected afterwards"] + #[doc = " to continue that computation."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param out destination buffer for the hash output."] + pub fn br_sha256_out(ctx: *const br_sha256_context, out: *mut libc::c_void); +} +extern "C" { + pub static br_sha384_vtable: br_hash_class; +} +#[doc = " \\brief SHA-384 context."] +#[doc = ""] +#[doc = " First field is a pointer to the vtable; it is set by the initialisation"] +#[doc = " function. Other fields are not supposed to be accessed by user code."] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct br_sha384_context { + #[doc = " \\brief Pointer to vtable for this context."] + pub vtable: *const br_hash_class, + pub buf: [libc::c_uchar; 128usize], + pub count: u64, + pub val: [u64; 8usize], +} +#[test] +fn bindgen_test_layout_br_sha384_context() { + assert_eq!( + ::core::mem::size_of::(), + 208usize, + concat!("Size of: ", stringify!(br_sha384_context)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(br_sha384_context)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).vtable as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_sha384_context), + "::", + stringify!(vtable) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).buf as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(br_sha384_context), + "::", + stringify!(buf) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).count as *const _ as usize }, + 136usize, + concat!( + "Offset of field: ", + stringify!(br_sha384_context), + "::", + stringify!(count) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).val as *const _ as usize }, + 144usize, + concat!( + "Offset of field: ", + stringify!(br_sha384_context), + "::", + stringify!(val) + ) + ); +} +impl Default for br_sha384_context { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +extern "C" { + #[doc = " \\brief SHA-384 context initialisation."] + #[doc = ""] + #[doc = " This function initialises or resets a context for a new SHA-384"] + #[doc = " computation. It also sets the vtable pointer."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + pub fn br_sha384_init(ctx: *mut br_sha384_context); +} +extern "C" { + #[doc = " \\brief Inject some data bytes in a running SHA-384 computation."] + #[doc = ""] + #[doc = " The provided context is updated with some data bytes. If the number"] + #[doc = " of bytes (`len`) is zero, then the data pointer (`data`) is ignored"] + #[doc = " and may be `NULL`, and this function does nothing."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param data pointer to the injected data."] + #[doc = " \\param len injected data length (in bytes)."] + pub fn br_sha384_update(ctx: *mut br_sha384_context, data: *const libc::c_void, len: usize); +} +extern "C" { + #[doc = " \\brief Compute SHA-384 output."] + #[doc = ""] + #[doc = " The SHA-384 output for the concatenation of all bytes injected in the"] + #[doc = " provided context since the last initialisation or reset call, is"] + #[doc = " computed and written in the buffer pointed to by `out`. The context"] + #[doc = " itself is not modified, so extra bytes may be injected afterwards"] + #[doc = " to continue that computation."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param out destination buffer for the hash output."] + pub fn br_sha384_out(ctx: *const br_sha384_context, out: *mut libc::c_void); +} +extern "C" { + #[doc = " \\brief Save SHA-384 running state."] + #[doc = ""] + #[doc = " The running state for SHA-384 (output of the last internal block"] + #[doc = " processing) is written in the buffer pointed to by `out`. The"] + #[doc = " number of bytes injected since the last initialisation or reset"] + #[doc = " call is returned. The context is not modified."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param out destination buffer for the running state."] + #[doc = " \\return the injected total byte length."] + pub fn br_sha384_state(ctx: *const br_sha384_context, out: *mut libc::c_void) -> u64; +} +extern "C" { + #[doc = " \\brief Restore SHA-384 running state."] + #[doc = ""] + #[doc = " The running state for SHA-384 is set to the provided values."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param stb source buffer for the running state."] + #[doc = " \\param count the injected total byte length."] + pub fn br_sha384_set_state(ctx: *mut br_sha384_context, stb: *const libc::c_void, count: u64); +} +extern "C" { + pub static br_sha512_vtable: br_hash_class; +} +pub type br_sha512_context = br_sha384_context; +extern "C" { + #[doc = " \\brief SHA-512 context initialisation."] + #[doc = ""] + #[doc = " This function initialises or resets a context for a new SHA-512"] + #[doc = " computation. It also sets the vtable pointer."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + pub fn br_sha512_init(ctx: *mut br_sha512_context); +} +extern "C" { + #[doc = " \\brief Compute SHA-512 output."] + #[doc = ""] + #[doc = " The SHA-512 output for the concatenation of all bytes injected in the"] + #[doc = " provided context since the last initialisation or reset call, is"] + #[doc = " computed and written in the buffer pointed to by `out`. The context"] + #[doc = " itself is not modified, so extra bytes may be injected afterwards"] + #[doc = " to continue that computation."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param out destination buffer for the hash output."] + pub fn br_sha512_out(ctx: *const br_sha512_context, out: *mut libc::c_void); +} +extern "C" { + pub static br_md5sha1_vtable: br_hash_class; +} +#[doc = " \\brief MD5+SHA-1 context."] +#[doc = ""] +#[doc = " First field is a pointer to the vtable; it is set by the initialisation"] +#[doc = " function. Other fields are not supposed to be accessed by user code."] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct br_md5sha1_context { + #[doc = " \\brief Pointer to vtable for this context."] + pub vtable: *const br_hash_class, + pub buf: [libc::c_uchar; 64usize], + pub count: u64, + pub val_md5: [u32; 4usize], + pub val_sha1: [u32; 5usize], +} +#[test] +fn bindgen_test_layout_br_md5sha1_context() { + assert_eq!( + ::core::mem::size_of::(), + 120usize, + concat!("Size of: ", stringify!(br_md5sha1_context)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(br_md5sha1_context)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).vtable as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_md5sha1_context), + "::", + stringify!(vtable) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).buf as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(br_md5sha1_context), + "::", + stringify!(buf) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).count as *const _ as usize }, + 72usize, + concat!( + "Offset of field: ", + stringify!(br_md5sha1_context), + "::", + stringify!(count) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).val_md5 as *const _ as usize }, + 80usize, + concat!( + "Offset of field: ", + stringify!(br_md5sha1_context), + "::", + stringify!(val_md5) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).val_sha1 as *const _ as usize }, + 96usize, + concat!( + "Offset of field: ", + stringify!(br_md5sha1_context), + "::", + stringify!(val_sha1) + ) + ); +} +impl Default for br_md5sha1_context { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +extern "C" { + #[doc = " \\brief MD5+SHA-1 context initialisation."] + #[doc = ""] + #[doc = " This function initialises or resets a context for a new SHA-512"] + #[doc = " computation. It also sets the vtable pointer."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + pub fn br_md5sha1_init(ctx: *mut br_md5sha1_context); +} +extern "C" { + #[doc = " \\brief Inject some data bytes in a running MD5+SHA-1 computation."] + #[doc = ""] + #[doc = " The provided context is updated with some data bytes. If the number"] + #[doc = " of bytes (`len`) is zero, then the data pointer (`data`) is ignored"] + #[doc = " and may be `NULL`, and this function does nothing."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param data pointer to the injected data."] + #[doc = " \\param len injected data length (in bytes)."] + pub fn br_md5sha1_update(ctx: *mut br_md5sha1_context, data: *const libc::c_void, len: usize); +} +extern "C" { + #[doc = " \\brief Compute MD5+SHA-1 output."] + #[doc = ""] + #[doc = " The MD5+SHA-1 output for the concatenation of all bytes injected in the"] + #[doc = " provided context since the last initialisation or reset call, is"] + #[doc = " computed and written in the buffer pointed to by `out`. The context"] + #[doc = " itself is not modified, so extra bytes may be injected afterwards"] + #[doc = " to continue that computation."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param out destination buffer for the hash output."] + pub fn br_md5sha1_out(ctx: *const br_md5sha1_context, out: *mut libc::c_void); +} +extern "C" { + #[doc = " \\brief Save MD5+SHA-1 running state."] + #[doc = ""] + #[doc = " The running state for MD5+SHA-1 (output of the last internal block"] + #[doc = " processing) is written in the buffer pointed to by `out`. The"] + #[doc = " number of bytes injected since the last initialisation or reset"] + #[doc = " call is returned. The context is not modified."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param out destination buffer for the running state."] + #[doc = " \\return the injected total byte length."] + pub fn br_md5sha1_state(ctx: *const br_md5sha1_context, out: *mut libc::c_void) -> u64; +} +extern "C" { + #[doc = " \\brief Restore MD5+SHA-1 running state."] + #[doc = ""] + #[doc = " The running state for MD5+SHA-1 is set to the provided values."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param stb source buffer for the running state."] + #[doc = " \\param count the injected total byte length."] + pub fn br_md5sha1_set_state(ctx: *mut br_md5sha1_context, stb: *const libc::c_void, count: u64); +} +#[doc = " \\brief Aggregate context for configurable hash function support."] +#[doc = ""] +#[doc = " The `br_hash_compat_context` type is a type which is large enough to"] +#[doc = " serve as context for all standard hash functions defined above."] +#[repr(C)] +#[derive(Copy, Clone)] +pub union br_hash_compat_context { + pub vtable: *const br_hash_class, + pub md5: br_md5_context, + pub sha1: br_sha1_context, + pub sha224: br_sha224_context, + pub sha256: br_sha256_context, + pub sha384: br_sha384_context, + pub sha512: br_sha512_context, + pub md5sha1: br_md5sha1_context, + _bindgen_union_align: [u64; 26usize], +} +#[test] +fn bindgen_test_layout_br_hash_compat_context() { + assert_eq!( + ::core::mem::size_of::(), + 208usize, + concat!("Size of: ", stringify!(br_hash_compat_context)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(br_hash_compat_context)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).vtable as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_hash_compat_context), + "::", + stringify!(vtable) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).md5 as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_hash_compat_context), + "::", + stringify!(md5) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).sha1 as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_hash_compat_context), + "::", + stringify!(sha1) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).sha224 as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_hash_compat_context), + "::", + stringify!(sha224) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).sha256 as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_hash_compat_context), + "::", + stringify!(sha256) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).sha384 as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_hash_compat_context), + "::", + stringify!(sha384) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).sha512 as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_hash_compat_context), + "::", + stringify!(sha512) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).md5sha1 as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_hash_compat_context), + "::", + stringify!(md5sha1) + ) + ); +} +impl Default for br_hash_compat_context { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +#[doc = " \\brief Multi-hasher context structure."] +#[doc = ""] +#[doc = " The multi-hasher runs up to six hash functions in the standard TLS list"] +#[doc = " (MD5, SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512) in parallel, over"] +#[doc = " the same input."] +#[doc = ""] +#[doc = " The multi-hasher does _not_ follow the OOP structure with a vtable."] +#[doc = " Instead, it is configured with the vtables of the hash functions it"] +#[doc = " should run. Structure fields are not supposed to be accessed directly."] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct br_multihash_context { + pub buf: [libc::c_uchar; 128usize], + pub count: u64, + pub val_32: [u32; 25usize], + pub val_64: [u64; 16usize], + pub impl_: [*const br_hash_class; 6usize], +} +#[test] +fn bindgen_test_layout_br_multihash_context() { + assert_eq!( + ::core::mem::size_of::(), + 416usize, + concat!("Size of: ", stringify!(br_multihash_context)) + ); + assert_eq!( + ::core::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(br_multihash_context)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).buf as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(br_multihash_context), + "::", + stringify!(buf) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).count as *const _ as usize }, + 128usize, + concat!( + "Offset of field: ", + stringify!(br_multihash_context), + "::", + stringify!(count) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).val_32 as *const _ as usize }, + 136usize, + concat!( + "Offset of field: ", + stringify!(br_multihash_context), + "::", + stringify!(val_32) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).val_64 as *const _ as usize }, + 240usize, + concat!( + "Offset of field: ", + stringify!(br_multihash_context), + "::", + stringify!(val_64) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::())).impl_ as *const _ as usize }, + 368usize, + concat!( + "Offset of field: ", + stringify!(br_multihash_context), + "::", + stringify!(impl_) + ) + ); +} +impl Default for br_multihash_context { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} +extern "C" { + #[doc = " \\brief Clear a multi-hasher context."] + #[doc = ""] + #[doc = " This should always be called once on a given context, _before_ setting"] + #[doc = " the implementation pointers."] + #[doc = ""] + #[doc = " \\param ctx the multi-hasher context."] + pub fn br_multihash_zero(ctx: *mut br_multihash_context); +} +extern "C" { + #[doc = " \\brief Reset a multi-hasher context."] + #[doc = ""] + #[doc = " This function prepares the context for a new hashing computation,"] + #[doc = " for all implementations configured at that point."] + #[doc = ""] + #[doc = " \\param ctx the multi-hasher context."] + pub fn br_multihash_init(ctx: *mut br_multihash_context); +} +extern "C" { + #[doc = " \\brief Inject some data bytes in a running multi-hashing computation."] + #[doc = ""] + #[doc = " The provided context is updated with some data bytes. If the number"] + #[doc = " of bytes (`len`) is zero, then the data pointer (`data`) is ignored"] + #[doc = " and may be `NULL`, and this function does nothing."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param data pointer to the injected data."] + #[doc = " \\param len injected data length (in bytes)."] + pub fn br_multihash_update( + ctx: *mut br_multihash_context, + data: *const libc::c_void, + len: usize, + ); +} +extern "C" { + #[doc = " \\brief Compute a hash output from a multi-hasher."] + #[doc = ""] + #[doc = " The hash output for the concatenation of all bytes injected in the"] + #[doc = " provided context since the last initialisation or reset call, is"] + #[doc = " computed and written in the buffer pointed to by `dst`. The hash"] + #[doc = " function to use is identified by `id` and must be one of the standard"] + #[doc = " hash functions. If that hash function was indeed configured in the"] + #[doc = " multi-hasher context, the corresponding hash value is written in"] + #[doc = " `dst` and its length (in bytes) is returned. If the hash function"] + #[doc = " was _not_ configured, then nothing is written in `dst` and 0 is"] + #[doc = " returned."] + #[doc = ""] + #[doc = " The context itself is not modified, so extra bytes may be injected"] + #[doc = " afterwards to continue the hash computations."] + #[doc = ""] + #[doc = " \\param ctx pointer to the context structure."] + #[doc = " \\param id the hash function symbolic identifier."] + #[doc = " \\param dst destination buffer for the hash output."] + #[doc = " \\return the hash output length (in bytes), or 0."] + pub fn br_multihash_out( + ctx: *const br_multihash_context, + id: libc::c_int, + dst: *mut libc::c_void, + ) -> usize; +} +#[doc = " \\brief Type for a GHASH implementation."] +#[doc = ""] +#[doc = " GHASH is a sort of keyed hash meant to be used to implement GCM in"] +#[doc = " combination with a block cipher (with 16-byte blocks)."] +#[doc = ""] +#[doc = " The `y` array has length 16 bytes and is used for input and output; in"] +#[doc = " a complete GHASH run, it starts with an all-zero value. `h` is a 16-byte"] +#[doc = " value that serves as key (it is derived from the encryption key in GCM,"] +#[doc = " using the block cipher). The data length (`len`) is expressed in bytes."] +#[doc = " The `y` array is updated."] +#[doc = ""] +#[doc = " If the data length is not a multiple of 16, then the data is implicitly"] +#[doc = " padded with zeros up to the next multiple of 16. Thus, when using GHASH"] +#[doc = " in GCM, this method may be called twice, for the associated data and"] +#[doc = " for the ciphertext, respectively; the zero-padding implements exactly"] +#[doc = " the GCM rules."] +#[doc = ""] +#[doc = " \\param y the array to update."] +#[doc = " \\param h the GHASH key."] +#[doc = " \\param data the input data (may be `NULL` if `len` is zero)."] +#[doc = " \\param len the input data length (in bytes)."] +pub type br_ghash = ::core::option::Option< + unsafe extern "C" fn( + y: *mut libc::c_void, + h: *const libc::c_void, + data: *const libc::c_void, + len: usize, + ), +>; +extern "C" { + #[doc = " \\brief GHASH implementation using multiplications (mixed 32-bit)."] + #[doc = ""] + #[doc = " This implementation uses multiplications of 32-bit values, with a"] + #[doc = " 64-bit result. It is constant-time (if multiplications are"] + #[doc = " constant-time)."] + #[doc = ""] + #[doc = " \\param y the array to update."] + #[doc = " \\param h the GHASH key."] + #[doc = " \\param data the input data (may be `NULL` if `len` is zero)."] + #[doc = " \\param len the input data length (in bytes)."] + pub fn br_ghash_ctmul( + y: *mut libc::c_void, + h: *const libc::c_void, + data: *const libc::c_void, + len: usize, + ); +} +extern "C" { + #[doc = " \\brief GHASH implementation using multiplications (strict 32-bit)."] + #[doc = ""] + #[doc = " This implementation uses multiplications of 32-bit values, with a"] + #[doc = " 32-bit result. It is usually somewhat slower than `br_ghash_ctmul()`,"] + #[doc = " but it is expected to be faster on architectures for which the"] + #[doc = " 32-bit multiplication opcode does not yield the upper 32 bits of the"] + #[doc = " product. It is constant-time (if multiplications are constant-time)."] + #[doc = ""] + #[doc = " \\param y the array to update."] + #[doc = " \\param h the GHASH key."] + #[doc = " \\param data the input data (may be `NULL` if `len` is zero)."] + #[doc = " \\param len the input data length (in bytes)."] + pub fn br_ghash_ctmul32( + y: *mut libc::c_void, + h: *const libc::c_void, + data: *const libc::c_void, + len: usize, + ); +} +extern "C" { + #[doc = " \\brief GHASH implementation using multiplications (64-bit)."] + #[doc = ""] + #[doc = " This implementation uses multiplications of 64-bit values, with a"] + #[doc = " 64-bit result. It is constant-time (if multiplications are"] + #[doc = " constant-time). It is substantially faster than `br_ghash_ctmul()`"] + #[doc = " and `br_ghash_ctmul32()` on most 64-bit architectures."] + #[doc = ""] + #[doc = " \\param y the array to update."] + #[doc = " \\param h the GHASH key."] + #[doc = " \\param data the input data (may be `NULL` if `len` is zero)."] + #[doc = " \\param len the input data length (in bytes)."] + pub fn br_ghash_ctmul64( + y: *mut libc::c_void, + h: *const libc::c_void, + data: *const libc::c_void, + len: usize, + ); +} +extern "C" { + #[doc = " \\brief GHASH implementation using the `pclmulqdq` opcode (part of the"] + #[doc = " AES-NI instructions)."] + #[doc = ""] + #[doc = " This implementation is available only on x86 platforms where the"] + #[doc = " compiler supports the relevant intrinsic functions. Even if the"] + #[doc = " compiler supports these functions, the local CPU might not support"] + #[doc = " the `pclmulqdq` opcode, meaning that a call will fail with an"] + #[doc = " illegal instruction exception. To safely obtain a pointer to this"] + #[doc = " function when supported (or 0 otherwise), use `br_ghash_pclmul_get()`."] + #[doc = ""] + #[doc = " \\param y the array to update."] + #[doc = " \\param h the GHASH key."] + #[doc = " \\param data the input data (may be `NULL` if `len` is zero)."] + #[doc = " \\param len the input data length (in bytes)."] + pub fn br_ghash_pclmul( + y: *mut libc::c_void, + h: *const libc::c_void, + data: *const libc::c_void, + len: usize, + ); +} +extern "C" { + #[doc = " \\brief Obtain the `pclmul` GHASH implementation, if available."] + #[doc = ""] + #[doc = " If the `pclmul` implementation was compiled in the library (depending"] + #[doc = " on the compiler abilities) _and_ the local CPU appears to support the"] + #[doc = " opcode, then this function will return a pointer to the"] + #[doc = " `br_ghash_pclmul()` function. Otherwise, it will return `0`."] + #[doc = ""] + #[doc = " \\return the `pclmul` GHASH implementation, or `0`."] + pub fn br_ghash_pclmul_get() -> br_ghash; +} +extern "C" { + #[doc = " \\brief GHASH implementation using the POWER8 opcodes."] + #[doc = ""] + #[doc = " This implementation is available only on POWER8 platforms (and later)."] + #[doc = " To safely obtain a pointer to this function when supported (or 0"] + #[doc = " otherwise), use `br_ghash_pwr8_get()`."] + #[doc = ""] + #[doc = " \\param y the array to update."] + #[doc = " \\param h the GHASH key."] + #[doc = " \\param data the input data (may be `NULL` if `len` is zero)."] + #[doc = " \\param len the input data length (in bytes)."] + pub fn br_ghash_pwr8( + y: *mut libc::c_void, + h: *const libc::c_void, + data: *const libc::c_void, + len: usize, + ); +} +extern "C" { + #[doc = " \\brief Obtain the `pwr8` GHASH implementation, if available."] + #[doc = ""] + #[doc = " If the `pwr8` implementation was compiled in the library (depending"] + #[doc = " on the compiler abilities) _and_ the local CPU appears to support the"] + #[doc = " opcode, then this function will return a pointer to the"] + #[doc = " `br_ghash_pwr8()` function. Otherwise, it will return `0`."] + #[doc = ""] + #[doc = " \\return the `pwr8` GHASH implementation, or `0`."] + pub fn br_ghash_pwr8_get() -> br_ghash; +} +extern "C" { + pub fn curve25519_donna( + arg1: *mut libc::c_uchar, + arg2: *const libc::c_uchar, + arg3: *const libc::c_uchar, + ) -> libc::c_int; +} +pub type __builtin_va_list = [__va_list_tag; 1usize]; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct __va_list_tag { + pub gp_offset: libc::c_uint, + pub fp_offset: libc::c_uint, + pub overflow_arg_area: *mut libc::c_void, + pub reg_save_area: *mut libc::c_void, +} +#[test] +fn bindgen_test_layout___va_list_tag() { + assert_eq!( + ::core::mem::size_of::<__va_list_tag>(), + 24usize, + concat!("Size of: ", stringify!(__va_list_tag)) + ); + assert_eq!( + ::core::mem::align_of::<__va_list_tag>(), + 8usize, + concat!("Alignment of ", stringify!(__va_list_tag)) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__va_list_tag>())).gp_offset as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(gp_offset) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__va_list_tag>())).fp_offset as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(fp_offset) + ) + ); + assert_eq!( + unsafe { + &(*(::core::ptr::null::<__va_list_tag>())).overflow_arg_area as *const _ as usize + }, + 8usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(overflow_arg_area) + ) + ); + assert_eq!( + unsafe { &(*(::core::ptr::null::<__va_list_tag>())).reg_save_area as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(reg_save_area) + ) + ); +} +impl Default for __va_list_tag { + fn default() -> Self { + unsafe { ::core::mem::zeroed() } + } +} diff --git a/enclave/sgxsd_ffi/src/ecalls.rs b/enclave/sgxsd_ffi/src/ecalls.rs new file mode 100644 index 0000000..b1e4650 --- /dev/null +++ b/enclave/sgxsd_ffi/src/ecalls.rs @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(clippy::all, clippy::option_unwrap_used, clippy::cast_sign_loss)] + +use alloc::boxed::{Box}; +use core::ptr; +use core::slice; + +use num_traits::{ToPrimitive}; + +pub use super::bindgen_wrapper::{ + sgxsd_msg_buf_t, + sgxsd_msg_from_t, +}; +use super::bindgen_wrapper::{ + sgxsd_enclave_server_noreply, + sgxsd_enclave_server_reply, +}; +use sgx_ffi::sgx::*; +use sgx_ffi::util::{clear}; + +pub trait SgxsdServer: Send + Sized { + type InitArgs; + type HandleCallArgs; + type TerminateArgs; + + fn init(_args: Option<&Self::InitArgs>) -> Result; + fn handle_call(&mut self, args: Option<&Self::HandleCallArgs>, request_data: &[u8], from: SgxsdMsgFrom) -> Result<(), (SgxStatus, SgxsdMsgFrom)>; + fn terminate(self, _args: Option<&Self::TerminateArgs>) -> Result<(), SgxStatus>; +} + +// wrap sgxsd_msg_from_t to make sure sgxsd_ocall_reply is called exactly once on it +unsafe impl Send for sgxsd_msg_from_t {} +pub struct SgxsdMsgFrom(Option>); +impl SgxsdMsgFrom { + fn new(from: &mut sgxsd_msg_from_t) -> Self { + let mut boxed_from = Box::new(sgxsd_msg_from_t { + valid: from.valid, + tag: from.tag, + server_key: Default::default(), + }); + boxed_from.server_key.data.copy_from_slice(&from.server_key.data); + let res = Self(Some(boxed_from)); + from.valid = false; + clear(&mut from.server_key.data[..]); + res + } + #[cfg(any(test, feature = "test"))] + pub fn mock() -> Self { + Self::new(&mut sgxsd_msg_from_t { tag: Default::default(), valid: true, server_key: Default::default() }) + } + pub fn reply(mut self, msg: &mut [u8]) -> Result<(), SgxStatus> { + if let Some(size) = msg.len().to_u32() { + let msg_buf = sgxsd_msg_buf_t { data: msg.as_mut_ptr(), size }; + if let Some(mut msg_from) = self.0.take() { + let msg_from_ref = &mut *msg_from; + match unsafe { sgxsd_enclave_server_reply(msg_buf, msg_from_ref) } { + 0 => Ok(()), + err => Err(err) + } + } else { + Err(SGX_ERROR_INVALID_STATE) + } + } else { + Err(SGX_ERROR_UNEXPECTED) + } + } + fn forget(mut self) { + if let Some(mut from) = self.0.take() { + from.valid = false; + clear(&mut from.server_key.data[..]); + } + } +} +impl Drop for SgxsdMsgFrom { + fn drop(&mut self) { + if let Some(mut from) = self.0.take() { + let from_ref = &mut *from; + unsafe { sgxsd_enclave_server_noreply(from_ref) }; + } + } +} + +pub fn sgxsd_enclave_server_init(p_args: *const S::InitArgs, + pp_state: *mut *mut S) + -> SgxStatus +where S: SgxsdServer, +{ + let args = unsafe { p_args.as_ref() }; + match S::init(args) { + Ok(new_state) => { + unsafe { *pp_state = Box::into_raw(Box::new(new_state)) }; + 0 + } + Err(err) => err + } +} + +pub fn sgxsd_enclave_server_handle_call(p_args: *const S::HandleCallArgs, + msg_buf: sgxsd_msg_buf_t, + from: &mut sgxsd_msg_from_t, + pp_state: *mut *mut S) + -> SgxStatus +where S: SgxsdServer, +{ + let args = unsafe { p_args.as_ref() }; + let mut state = unsafe { Box::from_raw(*pp_state) }; + let msg = ECallSlice(ptr::NonNull::new(msg_buf.data as *mut _), msg_buf.size as usize); + match state.handle_call(args, msg.as_ref(), SgxsdMsgFrom::new(from)) { + Ok(()) => { + unsafe { *pp_state = Box::into_raw(state) }; + 0 + } + Err((error, from)) => { + unsafe { *pp_state = Box::into_raw(state) }; + from.forget(); + error + } + } +} + +pub fn sgxsd_enclave_server_terminate(p_args: *const S::TerminateArgs, + p_state: *mut S) + -> SgxStatus +where S: SgxsdServer, +{ + let args = unsafe { p_args.as_ref() }; + let state = unsafe { Box::from_raw(p_state) }; + match state.terminate(args) { + Ok(()) => 0, + Err(err) => err + } +} + +struct ECallSlice(Option>, usize); +impl AsRef<[u8]> for ECallSlice { + fn as_ref(&self) -> &[u8] { + if self.1 != 0 { + if let Some(ptr) = self.0 { + unsafe { slice::from_raw_parts(ptr.as_ptr(), self.1) } + } else { + &[] + } + } else { + &[] + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::super::mocks; + use mockers::{*, matchers::*}; + + use super::super::bindgen_wrapper::{ + sgxsd_server_init_args_t, + sgxsd_server_handle_call_args_t, + sgxsd_server_terminate_args_t, + }; + + fn expect_msg_from_drop(scenario: &Scenario, msg_from: &sgxsd_msg_from_t) { + let msg_from = *msg_from; + let mock = test_ffi::mock_for(&mocks::SGXSD_ENCLAVE_SERVER_NOREPLY, &scenario); + scenario.expect(mock.sgxsd_enclave_server_noreply( + check(move |check_msg_from: &sgxsd_msg_from_t| + unsafe { check_msg_from.tag.__bindgen_anon_1.tag == msg_from.tag.__bindgen_anon_1.tag } && + check_msg_from.server_key.data == msg_from.server_key.data) + ).and_return(0)); + } + + #[test] + fn msg_from_drop() { + let scenario = Scenario::new(); + + let mut reply_from: sgxsd_msg_from_t = test_ffi::rand(); + + expect_msg_from_drop(&scenario, &reply_from); + drop(SgxsdMsgFrom::new(&mut reply_from)); + drop(scenario); + test_ffi::clear(&mocks::SGXSD_ENCLAVE_SERVER_NOREPLY); + } + + #[test] + fn msg_from_reply() { + let scenario = Scenario::new(); + + let reply_data: Box<[u8; 32]> = Box::new(test_ffi::rand()); + let mut reply_data_2 = reply_data.clone(); + + let reply_from: sgxsd_msg_from_t = test_ffi::rand(); + let mut reply_from_2 = reply_from.clone(); + + let sgxsd_enclave_server_reply = test_ffi::mock_for(&mocks::SGXSD_ENCLAVE_SERVER_REPLY, &scenario); + scenario.expect(sgxsd_enclave_server_reply + .sgxsd_enclave_server_reply( + check(move |msg_buf| *msg_buf == &reply_data[..]), + check(move |msg_from: &sgxsd_msg_from_t| + unsafe { msg_from.tag.__bindgen_anon_1.tag == reply_from.tag.__bindgen_anon_1.tag } && + msg_from.server_key.data == reply_from.server_key.data) + ).and_return(0)); + + SgxsdMsgFrom::new(&mut reply_from_2).reply(&mut reply_data_2[..]).unwrap(); + drop(scenario); + + test_ffi::clear(&mocks::SGXSD_ENCLAVE_SERVER_REPLY); + } + + struct MockSgxsdServer {} + impl SgxsdServer for MockSgxsdServer { + type InitArgs = sgxsd_server_init_args_t; + type HandleCallArgs = sgxsd_server_handle_call_args_t; + type TerminateArgs = sgxsd_server_terminate_args_t; + fn init(_args: Option<&Self::InitArgs>) -> Result { + Ok(Self {}) + } + fn handle_call(&mut self, _args: Option<&Self::HandleCallArgs>, _request_data: &[u8], _from: SgxsdMsgFrom) -> Result<(), (SgxStatus, SgxsdMsgFrom)> { + Ok(()) + } + fn terminate(self, _args: Option<&Self::TerminateArgs>) -> Result<(), SgxStatus> { + Ok(()) + } + } + + fn mock_sgxsd_server() -> Box<*mut MockSgxsdServer> { + let state = Box::new(MockSgxsdServer {}); + Box::new(Box::into_raw(state)) + } + + #[test] + fn sgxsd_enclave_server_init_null_args() { + let mut state: Box<*mut MockSgxsdServer> = Box::new(std::ptr::null_mut()); + sgxsd_enclave_server_init(std::ptr::null(), &mut *state); + } + + #[test] + fn sgxsd_enclave_server_handle_call_null_args() { + let scenario = Scenario::new(); + + let mut msg_from = test_ffi::rand(); + let mut pp_state = mock_sgxsd_server(); + + expect_msg_from_drop(&scenario, &msg_from); + sgxsd_enclave_server_handle_call( + std::ptr::null(), + mocks::valid_msg_buf(), + &mut msg_from, &mut *pp_state + ); + + unsafe { Box::from_raw(*pp_state) }; + + drop(scenario); + test_ffi::clear(&mocks::SGXSD_ENCLAVE_SERVER_NOREPLY); + } + + #[test] + fn sgxsd_enclave_server_handle_call_empty_msg() { + let scenario = Scenario::new(); + + let mut msg_from = test_ffi::rand(); + let mut pp_state = mock_sgxsd_server(); + + expect_msg_from_drop(&scenario, &msg_from); + assert_eq!(sgxsd_enclave_server_handle_call( + std::ptr::null(), + sgxsd_msg_buf_t { data: std::ptr::null_mut(), size: 0 }, + &mut msg_from, &mut *pp_state + ), 0); + + unsafe { Box::from_raw(*pp_state) }; + + drop(scenario); + test_ffi::clear(&mocks::SGXSD_ENCLAVE_SERVER_NOREPLY); + } + + #[test] + fn sgxsd_enclave_server_handle_call_valid_msg() { + let scenario = Scenario::new(); + + let mut msg_from = test_ffi::rand(); + let mut pp_state = mock_sgxsd_server(); + + expect_msg_from_drop(&scenario, &msg_from); + assert_eq!(sgxsd_enclave_server_handle_call( + std::ptr::null(), + mocks::valid_msg_buf(), + &mut msg_from, &mut *pp_state + ), 0); + + unsafe { Box::from_raw(*pp_state) }; + + drop(scenario); + test_ffi::clear(&mocks::SGXSD_ENCLAVE_SERVER_NOREPLY); + } + + #[test] + fn sgxsd_enclave_server_terminate_null_args() { + let pp_state = mock_sgxsd_server(); + sgxsd_enclave_server_terminate(std::ptr::null(), *pp_state); + } +} diff --git a/enclave/sgxsd_ffi/src/lib.rs b/enclave/sgxsd_ffi/src/lib.rs new file mode 100644 index 0000000..1b51094 --- /dev/null +++ b/enclave/sgxsd_ffi/src/lib.rs @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![cfg_attr(not(any(test, feature = "test")), no_std)] +#![allow( + unused_parens, + clippy::style, + clippy::large_enum_variant, +)] +#![warn( + bare_trait_objects, + elided_lifetimes_in_paths, + trivial_numeric_casts, + variant_size_differences, + clippy::integer_arithmetic, +)] +#![deny( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + clippy::cast_sign_loss, + clippy::clone_on_ref_ptr, + clippy::expl_impl_clone_on_copy, + clippy::explicit_into_iter_loop, + clippy::explicit_iter_loop, + clippy::float_arithmetic, + clippy::float_cmp_const, + clippy::indexing_slicing, + clippy::maybe_infinite_iter, + clippy::mem_forget, + clippy::missing_const_for_fn, + clippy::multiple_inherent_impl, + clippy::mut_mut, + clippy::needless_borrow, + clippy::option_unwrap_used, + clippy::panicking_unwrap, + clippy::print_stdout, + clippy::redundant_clone, + clippy::replace_consts, + clippy::result_unwrap_used, + clippy::shadow_unrelated, + clippy::unimplemented, + clippy::use_debug, + clippy::use_self, + clippy::use_underscore_binding, + clippy::wildcard_enum_match_arm, +)] + +extern crate alloc; + +#[allow(dead_code, non_camel_case_types, non_upper_case_globals, non_snake_case, improper_ctypes, clippy::all, clippy::pedantic, clippy::integer_arithmetic)] +mod bindgen_wrapper; +pub mod ecalls; + +#[cfg(any(test, feature = "test"))] pub mod mocks; + +use core::ffi::{c_void}; +use core::num; +use core::ptr; +use core::sync; + +use num_traits::{ToPrimitive}; +use rand_core::{CryptoRng, RngCore}; +use sgx_ffi::util::{clear, SecretValue}; + +use bindgen_wrapper::{ + br_sha256_context, + br_sha256_out, + br_sha256_init, + br_sha224_update, + br_sha256_SIZE, + curve25519_donna, + sgxsd_aes_gcm_decrypt, + sgxsd_aes_gcm_encrypt, + sgxsd_enclave_read_rand, + sgxsd_rand_buf, + sgx_status_t as SgxStatus, + SGX_SUCCESS, + SGX_ERROR_INVALID_PARAMETER, +}; + +// +// public API +// + +pub struct RdRand; + +pub struct AesGcmKey { + key: SecretValue, +} + +pub use bindgen_wrapper::sgxsd_aes_gcm_key; + +pub use bindgen_wrapper::sgxsd_aes_gcm_iv; +pub type AesGcmIv = sgxsd_aes_gcm_iv; + +pub use bindgen_wrapper::sgxsd_aes_gcm_mac; +pub type AesGcmMac = sgxsd_aes_gcm_mac; + +pub struct SHA256Context { + context: br_sha256_context, +} + +pub struct Curve25519Key { + privkey: SecretValue<[u8; 32]>, + pubkey: [u8; 32], +} + +// +// RdRand impls +// + +impl CryptoRng for RdRand {} +impl RngCore for RdRand { + fn next_u32(&mut self) -> u32 { + let random_bytes = self.rand_bytes([0; 4]); + u32::from_ne_bytes(random_bytes) + } + fn next_u64(&mut self) -> u64 { + let random_bytes = self.rand_bytes([0; 8]); + u64::from_ne_bytes(random_bytes) + } + fn fill_bytes(&mut self, dest: &mut [u8]) { + while let Err(_) = self.try_fill_bytes(dest) { + sync::atomic::spin_loop_hint(); + } + } + fn try_fill_bytes(&mut self, mut dest: &mut [u8]) -> Result<(), rand_core::Error> { + let mut rand_buf = sgxsd_rand_buf::default(); + while !dest.is_empty() { + match num::NonZeroU32::new(unsafe { sgxsd_enclave_read_rand(&mut rand_buf) }) { + None => (), + Some(error) => { + clear(&mut rand_buf.x); + return Err(error.into()); + } + } + let dest_part_len = rand_buf.x.len().min(dest.len()); + let (dest_part, dest_rest) = dest.split_at_mut(dest_part_len); + dest_part.copy_from_slice(rand_buf.x.get(..dest_part_len).unwrap_or_else(|| unreachable!())); + dest = dest_rest; + } + clear(&mut rand_buf.x); + Ok(()) + } +} +impl RdRand { + pub fn rand_bytes(&mut self, mut buf: T) -> T where T: AsMut<[u8]> { + self.fill_bytes(buf.as_mut()); + buf + } +} + +// +// sgxsd_aes_gcm_key impls +// + +impl AsRef<[u8]> for sgxsd_aes_gcm_key { + fn as_ref(&self) -> &[u8] { + &self.data + } +} + +impl AsMut<[u8]> for sgxsd_aes_gcm_key { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.data + } +} + +// +// AesGcmKey impls +// + +impl Default for AesGcmKey { + fn default() -> Self { + let mut new_self = Self { key: Default::default() }; + RdRand.fill_bytes(&mut new_self.key.get_mut().data); + new_self + } +} + +impl AesGcmKey { + pub fn new(data: &[u8]) -> Result { + let mut new = Self { key: Default::default() }; + if data.len() != new.key.get().data.len() { + return Err(SGX_ERROR_INVALID_PARAMETER); + } + new.key.get_mut().data.copy_from_slice(data); + Ok(new) + } + + pub fn set_key(&mut self, data: &[u8; 32]) { + self.key.get_mut().data = *data; + } + + pub fn decrypt( + &self, + data: &mut [u8], + aad: &[u8], + iv: &AesGcmIv, + mac: &AesGcmMac, + ) -> Result<(), SgxStatus> + { + let data_len = data.len().to_u32().ok_or(SGX_ERROR_INVALID_PARAMETER)?; + let aad_len = aad.len().to_u32().ok_or(SGX_ERROR_INVALID_PARAMETER)?; + + match unsafe { + sgxsd_aes_gcm_decrypt(self.key.get(), data.as_ptr() as *const c_void, data_len, + data.as_mut_ptr() as *mut c_void, + iv, + aad.as_ptr() as *const c_void, aad_len, + mac) + } { + SGX_SUCCESS => Ok(()), + error => Err(error), + } + } + + pub fn encrypt( + &self, + data: &mut [u8], + aad: &[u8], + iv: &AesGcmIv, + mac: &mut AesGcmMac, + ) -> Result<(), SgxStatus> + { + let data_len = data.len().to_u32().ok_or(SGX_ERROR_INVALID_PARAMETER)?; + let aad_len = aad.len().to_u32().ok_or(SGX_ERROR_INVALID_PARAMETER)?; + + match unsafe { + sgxsd_aes_gcm_encrypt(self.key.get(), data.as_ptr() as *const c_void, data_len, + data.as_mut_ptr() as *mut c_void, + iv, + aad.as_ptr() as *const c_void, aad_len, + mac) + } { + SGX_SUCCESS => Ok(()), + error => Err(error), + } + } + + pub const fn len() -> usize { + let _ = sgxsd_aes_gcm_key { data: [0; 32] }; + 32 + } +} + +// +// SHA256Context impls +// + +impl SHA256Context { + pub const fn hash_len() -> usize { + br_sha256_SIZE as usize + } + pub fn reset(&mut self) { + unsafe { br_sha256_init(&mut self.context) }; + } + pub fn update(&mut self, data: &[u8]) { + unsafe { br_sha224_update(&mut self.context, data.as_ptr() as *const c_void, data.len()) }; + } + pub fn result(&mut self, out: &mut [u8; Self::hash_len()]) { + unsafe { br_sha256_out(&self.context, out.as_mut_ptr() as *mut c_void) } + } +} + +unsafe impl Send for br_sha256_context {} +unsafe impl Sync for br_sha256_context {} +impl Default for SHA256Context { + fn default() -> Self { + let mut state = Self { + context: br_sha256_context { + vtable: ptr::null(), + buf: [0; 64], + count: Default::default(), + val: Default::default(), + }, + }; + state.reset(); + state + } +} + +// +// Curve25519 impls +// + +impl Curve25519Key { + pub fn set_key(&mut self, privkey: &[u8; 32]) { + *self.privkey.get_mut() = *privkey; + curve25519_base(&mut self.pubkey, self.privkey.get()); + } + #[allow(clippy::indexing_slicing)] + pub fn generate(&mut self, mut rng: impl RngCore) { + let privkey = self.privkey.get_mut(); + rng.fill_bytes(privkey); + privkey[0] &= 248; + privkey[31] &= 127; + privkey[31] |= 64; + curve25519_base(&mut self.pubkey, self.privkey.get()); + } + pub const fn pubkey(&self) -> &[u8; 32] { + &self.pubkey + } + pub fn privkey(&self) -> &[u8; 32] { + self.privkey.get() + } + pub fn dh(&self, pubkey: &[u8; 32], out: &mut [u8; 32]) { + curve25519(out, self.privkey.get(), pubkey); + } +} + +impl Default for Curve25519Key { + fn default() -> Self { + let mut new_self = Self { + privkey: Default::default(), + pubkey: Default::default(), + }; + new_self.generate(&mut RdRand); + new_self + } +} + +fn curve25519_base(mypublic: &mut [u8; 32], mysecret: &[u8; 32]) { + let mut basepoint = [0u8; 32]; + basepoint[0] = 9; + curve25519(mypublic, mysecret, &basepoint) +} + +fn curve25519(mypublic: &mut [u8; 32], mysecret: &[u8; 32], basepoint: &[u8; 32]) { + unsafe { curve25519_donna(mypublic.as_mut_ptr(), mysecret.as_ptr(), basepoint.as_ptr()) }; +} + +#[cfg(test)] +pub mod tests { + use super::*; + use super::mocks; + + use crate::bindgen_wrapper::{ + SGX_ERROR_UNEXPECTED, + }; + + use mockers::{Scenario}; + + const ASSERT_RANDOM_WINDOW_SIZE: usize = 2; + + fn assert_random(src: &[u8], dst: &[u8]) { + assert_eq!(src.len(), dst.len()); + for start in 0..=(src.len() - ASSERT_RANDOM_WINDOW_SIZE) { + let range = start..(start + ASSERT_RANDOM_WINDOW_SIZE); + assert_ne!(dst[range.clone()], src[range]); + } + } + + #[test] + #[should_panic] + fn test_assert_random() { + let src = test_ffi::rand_bytes(vec![0; 100]); + let mut data = src.clone(); + RdRand.fill_bytes(&mut data[..(src.len() - ASSERT_RANDOM_WINDOW_SIZE)]); + assert_random(&src, &data); + } + + #[test] + fn fill_bytes_ok() { + let src = test_ffi::rand_bytes(vec![0; 100]); + let mut data = src.clone(); + RdRand.fill_bytes(&mut data[..0]); + assert_eq!(data[..], src[..]); + RdRand.fill_bytes(&mut data); + assert_random(&src, &data); + } + + #[test] + fn try_fill_bytes_empty() { + let scenario = Scenario::new(); + + let read_rand_mock = test_ffi::mock_for(&mocks::SGXSD_ENCLAVE_READ_RAND, &scenario); + scenario.expect(read_rand_mock.sgxsd_enclave_read_rand().never()); + + let mut data = vec![0; 0]; + assert!(RdRand.try_fill_bytes(&mut data).is_ok()); + + test_ffi::clear(&mocks::SGXSD_ENCLAVE_READ_RAND); + } + + #[test] + fn try_fill_bytes_one() { + let scenario = Scenario::new(); + + let read_rand_mock = test_ffi::mock_for(&mocks::SGXSD_ENCLAVE_READ_RAND, &scenario); + scenario.expect(read_rand_mock.sgxsd_enclave_read_rand().and_return_clone(SGX_SUCCESS).times(1)); + + let mut data = vec![0; 1]; + assert!(RdRand.try_fill_bytes(&mut data).is_ok()); + + test_ffi::clear(&mocks::SGXSD_ENCLAVE_READ_RAND); + } + + #[test] + fn try_fill_bytes_small() { + let scenario = Scenario::new(); + + let read_rand_mock = test_ffi::mock_for(&mocks::SGXSD_ENCLAVE_READ_RAND, &scenario); + scenario.expect(read_rand_mock.sgxsd_enclave_read_rand().and_return_clone(SGX_SUCCESS).times(1)); + + let mut data = vec![0; ASSERT_RANDOM_WINDOW_SIZE]; + assert!(RdRand.try_fill_bytes(&mut data).is_ok()); + + test_ffi::clear(&mocks::SGXSD_ENCLAVE_READ_RAND); + } + + #[test] + fn try_fill_bytes_once() { + let scenario = Scenario::new(); + + let read_rand_mock = test_ffi::mock_for(&mocks::SGXSD_ENCLAVE_READ_RAND, &scenario); + scenario.expect(read_rand_mock.sgxsd_enclave_read_rand().and_return_clone(SGX_SUCCESS).times(1)); + + let src = test_ffi::rand_bytes(vec![0; std::mem::size_of::()]); + let mut data = src.clone(); + assert!(RdRand.try_fill_bytes(&mut data).is_ok()); + assert_random(&src, &data); + + test_ffi::clear(&mocks::SGXSD_ENCLAVE_READ_RAND); + } + + #[test] + fn try_fill_bytes_multiple() { + let scenario = Scenario::new(); + + let read_rand_mock = test_ffi::mock_for(&mocks::SGXSD_ENCLAVE_READ_RAND, &scenario); + scenario.expect(read_rand_mock.sgxsd_enclave_read_rand().and_return_clone(SGX_SUCCESS).times(4)); + + let src = test_ffi::rand_bytes(vec![0; std::mem::size_of::() * 4 - 1]); + let mut data = src.clone(); + assert!(RdRand.try_fill_bytes(&mut data).is_ok()); + assert_random(&src, &data); + + test_ffi::clear(&mocks::SGXSD_ENCLAVE_READ_RAND); + } + + #[test] + fn try_fill_bytes_error() { + let scenario = Scenario::new(); + + let read_rand_mock = test_ffi::mock_for(&mocks::SGXSD_ENCLAVE_READ_RAND, &scenario); + scenario.expect(read_rand_mock.sgxsd_enclave_read_rand().and_return(SGX_ERROR_UNEXPECTED)); + + let mut data = vec![0; 1]; + assert!(RdRand.try_fill_bytes(&mut data[..0]).is_ok()); + assert!(RdRand.try_fill_bytes(&mut data).is_err()); + + test_ffi::clear(&mocks::SGXSD_ENCLAVE_READ_RAND); + } + + #[test] + fn rand_bytes_valid() { + let src = test_ffi::rand_bytes(vec![0; 100]); + let data = RdRand.rand_bytes(src.clone()); + assert_eq!(data.len(), src.len()); + assert_random(&src, &data); + + let empty = RdRand.rand_bytes(vec![]); + assert_eq!(empty.len(), 0); + } +} diff --git a/enclave/sgxsd_ffi/src/mocks.rs b/enclave/sgxsd_ffi/src/mocks.rs new file mode 100644 index 0000000..a480955 --- /dev/null +++ b/enclave/sgxsd_ffi/src/mocks.rs @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(clippy::all, clippy::option_unwrap_used, clippy::cast_sign_loss, clippy::cast_possible_truncation)] + +use std::cell::{RefCell}; + +use mockers_derive::mocked; +use rand::*; +use rand::distributions::*; +use test_ffi::*; + +pub use super::bindgen_wrapper::{ + sgxsd_aes_gcm_key_t, + sgxsd_msg_buf_t, + sgxsd_msg_from_t, +}; + +use super::bindgen_wrapper::{ + br_sha224_context, + br_sha256_context, + SGXSD_SHA256_HASH_SIZE, + sgxsd_aes_gcm_iv_t, + sgxsd_aes_gcm_mac_t, + sgxsd_msg_tag_t, + sgxsd_msg_tag__bindgen_ty_1, + sgxsd_rand_buf_t, + sgx_status_t, +}; + +// +// mock extern "C" functions +// + +thread_local! { + pub static SGXSD_ENCLAVE_SERVER_NOREPLY: RefCell> = RefCell::new(None); + pub static SGXSD_ENCLAVE_SERVER_REPLY: RefCell> = RefCell::new(None); + pub static SGXSD_AES_GCM_ENCRYPT: RefCell> = RefCell::new(None); + pub static SGXSD_AES_GCM_DECRYPT: RefCell> = RefCell::new(None); + pub static SGXSD_ENCLAVE_READ_RAND: RefCell> = RefCell::new(None); +} + +impl std::fmt::Debug for sgxsd_msg_from_t { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) + } +} + +#[mocked] +pub trait SgxsdEnclaveServerNoreply { + fn sgxsd_enclave_server_noreply(&self, from: sgxsd_msg_from_t) -> sgx_status_t; +} + +#[mocked] +pub trait SgxsdEnclaveServerReply { + fn sgxsd_enclave_server_reply(&self, reply_buf: &[u8], from: sgxsd_msg_from_t) -> sgx_status_t; +} + +#[mocked] +pub trait SgxsdAesGcmEncrypt { + fn sgxsd_aes_gcm_encrypt(&self, key: &[u8], src: &[u8], iv: &[u8], aad: &[u8]) -> Result, ()>; +} + +#[mocked] +pub trait SgxsdAesGcmDecrypt { + fn sgxsd_aes_gcm_decrypt(&self, key: &[u8], src: &[u8], iv: &[u8], aad: &[u8]) -> Result, ()>; +} + +#[mocked] +pub trait SgxsdEnclaveReadRand { + fn sgxsd_enclave_read_rand(&self) -> sgx_status_t; +} + +// +// random mock values +// + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> sgxsd_msg_tag_t { + sgxsd_msg_tag_t { __bindgen_anon_1: sgxsd_msg_tag__bindgen_ty_1 { tag: rng.sample(self) } } + } +} +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> sgxsd_aes_gcm_key_t { + sgxsd_aes_gcm_key_t { data: rng.sample(self) } + } +} +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> sgxsd_msg_from_t { + sgxsd_msg_from_t { + tag: rng.sample(self), + valid: true, + server_key: rng.sample(self), + } + } +} + +// +// valid mock values +// + +lazy_static::lazy_static! { + static ref VALID_MSG_BUF: Vec = vec![0; 1]; +} + +pub fn valid_msg_buf() -> sgxsd_msg_buf_t { + let msg = &VALID_MSG_BUF; + sgxsd_msg_buf_t { data: msg.as_ptr() as *mut _, size: msg.len() as u32 } +} + +// +// mock extern "C" function implementations +// + +pub mod impls { + use super::*; + + #[no_mangle] + pub extern "C" fn sgxsd_enclave_server_noreply(from: *mut sgxsd_msg_from_t) -> sgx_status_t { + SGXSD_ENCLAVE_SERVER_NOREPLY.with(|mock| { + mock.borrow().as_ref().expect("no mock for sgxsd_enclave_server_noreply") + .sgxsd_enclave_server_noreply(unsafe { *from }) + }) + } + + #[no_mangle] + pub extern "C" fn sgxsd_enclave_server_reply(reply_buf: sgxsd_msg_buf_t, from: *mut sgxsd_msg_from_t) -> sgx_status_t { + assert!(!reply_buf.data.is_null()); + assert_ne!(reply_buf.size, 0); + let reply_buf = unsafe { std::slice::from_raw_parts_mut(reply_buf.data, reply_buf.size as usize) }; + SGXSD_ENCLAVE_SERVER_REPLY.with(|mock| { + mock.borrow().as_ref().expect("no mock for sgxsd_enclave_server_reply") + .sgxsd_enclave_server_reply(reply_buf, unsafe { *from }) + }) + } + + + #[no_mangle] + pub extern "C" fn sgxsd_aes_gcm_encrypt( + p_key: *const sgxsd_aes_gcm_key_t, + p_src: *const ::std::os::raw::c_void, + src_len: u32, + p_dst: *mut ::std::os::raw::c_void, + p_iv: *const sgxsd_aes_gcm_iv_t, + p_aad: *const ::std::os::raw::c_void, + aad_len: u32, + p_out_mac: *mut sgxsd_aes_gcm_mac_t, + ) -> sgx_status_t { + let key = unsafe { std::ptr::read_volatile(p_key) }; + assert_ne!(&key.data[..], &vec![0; key.data.len()][..]); + assert!(!p_iv.is_null()); + let iv = unsafe { std::ptr::read_volatile(p_iv) }; + let out_mac = unsafe { p_out_mac.as_mut().expect("p_out_mac is null") }; + read_rand(&mut out_mac.data); + if src_len != 0 { + assert!(!p_src.is_null()); + assert!(!p_dst.is_null()); + let src = unsafe { std::slice::from_raw_parts(p_src as *const u8, src_len as usize) }; + src.iter().for_each(|p| unsafe { std::ptr::read_volatile(p); }); + } + let aad = if aad_len != 0 { + assert!(!p_aad.is_null()); + let aad = unsafe { std::slice::from_raw_parts(p_aad as *const u8, aad_len as usize) }; + aad.iter().for_each(|p| unsafe { std::ptr::read_volatile(p); }); + aad + } else { + &[] + }; + SGXSD_AES_GCM_ENCRYPT.with(|mock| { + if let Some(mock) = mock.borrow().as_ref() { + let res = { + let src = unsafe { std::slice::from_raw_parts(p_src as *const u8, src_len as usize) }; + mock.sgxsd_aes_gcm_encrypt(&key.data, src, &iv.data, aad) + }; + match res { + Ok(data) => { + if src_len != 0 { + let dst = unsafe { std::slice::from_raw_parts_mut(p_dst as *mut u8, src_len as usize) }; + dst.copy_from_slice(&data); + } + 0 + } + Err(()) => { + let dst = unsafe { std::slice::from_raw_parts_mut(p_dst as *mut u8, src_len as usize) }; + dst.iter_mut().for_each(|b: &mut u8| *b = 0); + 1 + } + } + } else { + let dst = unsafe { std::slice::from_raw_parts_mut(p_dst as *mut u8, src_len as usize) }; + read_rand(dst); + 0 + } + }) + } + + #[no_mangle] + pub extern "C" fn sgxsd_aes_gcm_decrypt( + p_key: *const sgxsd_aes_gcm_key_t, + p_src: *const ::std::os::raw::c_void, + src_len: u32, + p_dst: *mut ::std::os::raw::c_void, + p_iv: *const sgxsd_aes_gcm_iv_t, + p_aad: *const ::std::os::raw::c_void, + aad_len: u32, + p_in_mac: *const sgxsd_aes_gcm_mac_t, + ) -> sgx_status_t { + let key = unsafe { std::ptr::read_volatile(p_key) }; + assert_ne!(&key.data[..], &vec![0; key.data.len()][..]); + assert!(!p_iv.is_null()); + let iv = unsafe { std::ptr::read_volatile(p_iv) }; + assert!(!p_in_mac.is_null()); + unsafe { std::ptr::read_volatile(p_in_mac) }; + if src_len != 0 { + assert!(!p_src.is_null()); + assert!(!p_dst.is_null()); + let src = unsafe { std::slice::from_raw_parts(p_src as *const u8, src_len as usize) }; + src.iter().for_each(|p| unsafe { std::ptr::read_volatile(p); }); + } + let aad = if aad_len != 0 { + assert!(!p_aad.is_null()); + let aad = unsafe { std::slice::from_raw_parts(p_aad as *const u8, aad_len as usize) }; + aad.iter().for_each(|p| unsafe { std::ptr::read_volatile(p); }); + aad + } else { + &[] + }; + SGXSD_AES_GCM_DECRYPT.with(|mock| { + if let Some(mock) = mock.borrow().as_ref() { + let res = { + let src = unsafe { std::slice::from_raw_parts(p_src as *const u8, src_len as usize) }; + mock.sgxsd_aes_gcm_decrypt(&key.data, src, &iv.data, aad) + }; + match res { + Ok(data) => { + if src_len != 0 { + let dst = unsafe { std::slice::from_raw_parts_mut(p_dst as *mut u8, src_len as usize) }; + dst.copy_from_slice(&data); + } + 0 + } + Err(()) => 1, + } + } else { + let dst = unsafe { std::slice::from_raw_parts_mut(p_dst as *mut u8, src_len as usize) }; + read_rand(dst); + 0 + } + }) + } + + #[no_mangle] + pub extern "C" fn sgxsd_enclave_read_rand(p_privkey: *mut sgxsd_rand_buf_t) -> sgx_status_t { + assert!(!p_privkey.is_null()); + SGXSD_ENCLAVE_READ_RAND.with(|mock| { + let res = if let Some(mock) = mock.borrow().as_ref() { + mock.sgxsd_enclave_read_rand() + } else { + 0 + }; + read_rand(unsafe { &mut (*p_privkey).x }); + res + }) + } + + #[no_mangle] + pub extern "C" fn br_sha256_init(ctx: *mut br_sha256_context) { + unsafe { std::ptr::write_volatile(ctx, std::mem::zeroed()) }; + } + + #[no_mangle] + pub extern "C" fn br_sha224_update(ctx: *mut br_sha224_context, data: *const ::std::os::raw::c_void, len: usize) { + unsafe { std::ptr::write_volatile(ctx, std::ptr::read_volatile(ctx)) }; + if len != 0 { + assert!(!data.is_null()); + let data = unsafe { std::slice::from_raw_parts(data as *const u8, len) }; + data.iter().for_each(|p| unsafe { std::ptr::read_volatile(p); }); + } + } + + #[no_mangle] + pub extern "C" fn br_sha256_out(ctx: *const br_sha256_context, out: *mut ::std::os::raw::c_void) { + unsafe { std::ptr::read_volatile(ctx) }; + assert!(!out.is_null()); + let out = unsafe { std::slice::from_raw_parts_mut(out as *mut u8, SGXSD_SHA256_HASH_SIZE as usize) }; + read_rand(out); + } +} diff --git a/enclave/test_ffi/.cargo/config b/enclave/test_ffi/.cargo/config new file mode 100644 index 0000000..fe9c8d6 --- /dev/null +++ b/enclave/test_ffi/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/target" diff --git a/enclave/test_ffi/Cargo.toml b/enclave/test_ffi/Cargo.toml new file mode 100644 index 0000000..eff34d8 --- /dev/null +++ b/enclave/test_ffi/Cargo.toml @@ -0,0 +1,13 @@ +[package] +authors = ["Open Whisper Systems"] +name = "test_ffi" +version = "0.1.0" +license = "AGPL-3.0-or-later" +edition = "2018" + +[dependencies] +libc = { version = "0.2", default-features = false, features = [] } +mockers = { version = "0.21" } +mockers_derive = { version = "0.21" } +rand = { version = "0.7", default-features = false, features = [] } +rand_chacha = { version = "0.2", default-features = false, features = [] } diff --git a/enclave/test_ffi/src/lib.rs b/enclave/test_ffi/src/lib.rs new file mode 100644 index 0000000..7479a08 --- /dev/null +++ b/enclave/test_ffi/src/lib.rs @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::cell::{RefCell}; +use std::thread::{LocalKey}; + +use mockers::*; +use rand::*; +use rand::distributions::*; +use rand_chacha::{ChaChaRng}; + +// +// mock extern "C" functions +// + +pub fn set(key: &'static LocalKey>>, mock: T) { + key.with(|key| *key.borrow_mut() = Some(mock)); +} + +pub fn clear(key: &'static LocalKey>>) { + key.with(|key| *key.borrow_mut() = None); +} + +pub fn mock_for(key: &'static LocalKey>>, scenario: &Scenario) + -> ::Handle where + T: Mock, +{ + let (mock, handle) = scenario.create_mock::(); + set(key, mock); + handle +} + +// +// random mock values +// + +pub fn rand_bytes(mut buf: T) -> T where T: AsMut<[u8]> { + read_rand(buf.as_mut()); + buf +} +pub fn rand() -> T where Standard: Distribution { + RAND_STATE.with(|rand| rand.borrow_mut().gen()) +} +pub fn read_rand(buf: &mut [u8]) { + RAND_STATE.with(|rand| rand.borrow_mut().fill_bytes(buf)); +} + +thread_local! { + static RAND_STATE: RefCell = RefCell::new(ChaChaRng::from_seed([0; 32])); +} diff --git a/service/.cargo/config b/service/.cargo/config new file mode 100644 index 0000000..207bc46 --- /dev/null +++ b/service/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "build/target" diff --git a/service/.gitignore b/service/.gitignore new file mode 100644 index 0000000..d6999b6 --- /dev/null +++ b/service/.gitignore @@ -0,0 +1,7 @@ +/build +/config/*.production.yml +/config/*.production.pem +/config/*.staging.yml +/config/*.staging.pem +/config/*.testing.yml +/config/*.testing.pem diff --git a/service/Cargo.lock b/service/Cargo.lock new file mode 100644 index 0000000..057e36d --- /dev/null +++ b/service/Cargo.lock @@ -0,0 +1,2106 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "arc-swap" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "arrayvec" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "autocfg" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "backtrace" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "c2-chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chrono" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "clear_on_drop" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cookie" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "crossbeam-deque" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-queue" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-utils" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "curve25519-dalek" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "digest" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dtoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "either" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "env_logger" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "exponential-decay-histogram" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fixedbitset" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-cpupool" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "generic-array" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "h2" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "string 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "http" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "http-body" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "httparse" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "humantime" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hyper" +version = "0.12.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "h2 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hyper-tls" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.30 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "indexmap" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "indoc" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "indoc-impl 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "indoc-impl" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", + "unindent 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "iovec" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itertools" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "itertools" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "kbupd" +version = "1.0.14" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "exponential-decay-histogram 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.30 (registry+https://github.com/rust-lang/crates.io-index)", + "kbupd_api 0.1.0", + "kbupd_client 0.1.0", + "kbupd_config 0.1.0", + "kbupd_util 0.1.0", + "kbuptlsd 1.0.1", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mockers 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mockers_derive 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "prost-build 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)", + "route-recognizer 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-async 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "tk-listen 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-signal 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "try_future 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kbupd_api" +version = "0.1.0" +dependencies = [ + "kbupd_util 0.1.0", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kbupd_api_client" +version = "0.1.0" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.30 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kbupd_api 0.1.0", + "kbupd_client 0.1.0", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "try_future 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kbupd_client" +version = "0.1.0" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "prost-build 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)", + "x25519-dalek 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kbupd_config" +version = "0.1.0" +dependencies = [ + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kbupd_util 0.1.0", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kbupd_util" +version = "0.1.0" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kbuptlsd" +version = "1.0.1" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.30 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rustunnel 0.1.0", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tk-listen 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "linked-hash-map" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lock_api" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memoffset" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mio" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio-uds" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mockers" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "select-rustc 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mockers_derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "indoc 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-quote 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "multimap" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "native-tls" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.50 (git+https://github.com/geogriff-signal/rust-openssl.git?rev=ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0)", + "schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "net2" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nix" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nodrop" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num-derive" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num_cpus" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "openssl" +version = "0.10.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.50 (git+https://github.com/geogriff-signal/rust-openssl.git?rev=ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0)", +] + +[[package]] +name = "openssl" +version = "0.10.25" +source = "git+https://github.com/geogriff-signal/rust-openssl.git?rev=ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0#ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.50 (git+https://github.com/geogriff-signal/rust-openssl.git?rev=ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0)", +] + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "openssl-sys" +version = "0.9.50" +source = "git+https://github.com/geogriff-signal/rust-openssl.git?rev=ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0#ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ordered-float" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "owning_ref" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "petgraph" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pkg-config" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ppv-lite86" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro-hack" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-quote" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-quote-impl 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-quote-impl" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "prost" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "prost-derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "prost-build" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "multimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "prost-types 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "prost-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "prost-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quick-error" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ring" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "route-recognizer" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-demangle" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustunnel" +version = "0.1.0" +dependencies = [ + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.25 (git+https://github.com/geogriff-signal/rust-openssl.git?rev=ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0)", + "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "seccomp-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "schannel" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scopeguard" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "seccomp-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "security-framework" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "security-framework-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "select-rustc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_yaml" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "signal-hook" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "signal-hook-registry 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "signal-hook-registry" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slog" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slog-async" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "slog 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "smallvec" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "spin" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "stable_deref_trait" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "string" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "subtle" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "0.15.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synstructure" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tempfile" +version = "3.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termcolor" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termion" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tk-listen" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-fs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-trace-core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-udp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-buf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-codec" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-current-thread" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-executor" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-fs" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-io" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-reactor" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-signal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "signal-hook 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-sync" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-tcp" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-threadpool" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-timer" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-trace-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-udp" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-uds" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "try-lock" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "try_future" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "typenum" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ucd-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-segmentation" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unindent" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "untrusted" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "utf8-ranges" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vcpkg" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "want" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "which" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wincolor" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "x25519-dalek" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "curve25519-dalek 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "yaml-rust" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" +"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" +"checksum backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)" = "ada4c783bb7e7443c14e0480f429ae2cc99da95065aeab7ee1b81ada0419404f" +"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" +"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" +"checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" +"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +"checksum clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" +"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" +"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" +"checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" +"checksum crossbeam-epoch 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" +"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" +"checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" +"checksum curve25519-dalek 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5d4b820e8711c211745880150f5fac78ab07d6e3851d8ce9f5a02cedc199174c" +"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" +"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" +"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" +"checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" +"checksum exponential-decay-histogram 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6b9544714ab2952693a34c67ade6b41c03dd6d5a614e80686ee73d6724277343" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "a2037ec1c6c1c4f79557762eab1f7eae1f64f6cb418ace90fae88f0942b60139" +"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" +"checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" +"checksum h2 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "69b2a5a3092cbebbc951fe55408402e696ee2ed09019137d1800fc2c411265d2" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e" +"checksum http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "eed324f0f0daf6ec10c474f150505af2c143f251722bf9dbd1261bd1f2ee2c1a" +"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" +"checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" +"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" +"checksum hyper 0.12.30 (registry+https://github.com/rust-lang/crates.io-index)" = "40e7692b2009a70b1e9b362284add4d8b75880fefddb4acaa5e67194e843f219" +"checksum hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f" +"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" +"checksum indoc 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1f59f228c76fda6ecd8dab79683039a7054c748587f682a911094f473647bd6" +"checksum indoc-impl 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "63f070ef080db3601c1a0ecc75c7bb35104cc0ce2d7c4e049952a96a61d8933b" +"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" +"checksum itertools 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "c4a9b56eb56058f43dc66e58f40a214b2ccbc9f3df51861b63d51dec7b65bc3f" +"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" +"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" +"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" +"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" +"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" +"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" +"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" +"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum mockers 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1534e2f6e41e75e90fc2732a69848e473ab8bba84e54e907649011329c6ee8ab" +"checksum mockers_derive 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e680ba61e47db30d0e7249f94b77ab279c2d2079356d1fd506d4caad149e339" +"checksum multimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb04b9f127583ed176e163fb9ec6f3e793b87e21deedd5734a69386a18a0151" +"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" +"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +"checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" +"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2" +"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" +"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" +"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" +"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +"checksum openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)" = "8152bb5a9b5b721538462336e3bef9a539f892715e5037fda0f984577311af15" +"checksum openssl 0.10.25 (git+https://github.com/geogriff-signal/rust-openssl.git?rev=ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0)" = "" +"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +"checksum openssl-sys 0.9.50 (git+https://github.com/geogriff-signal/rust-openssl.git?rev=ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0)" = "" +"checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" +"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" +"checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" +"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" +"checksum petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f" +"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" +"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" +"checksum proc-macro-hack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0c1dd4172a1e1f96f709341418f49b11ea6c2d95d53dca08c0f74cbd332d9cf3" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum proc-quote 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fa612543f23fda013e1e6ce30b5285a9d313c6e582e57b4ceca74eb5b85685b5" +"checksum proc-quote-impl 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f785f0f8cd00b7945efc3f3bdf8205eb06af5aacec598d83e67f41dc8d101fda" +"checksum prost 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96d14b1c185652833d24aaad41c5832b0be5616a590227c1fbff57c616754b23" +"checksum prost-build 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eb788126ea840817128183f8f603dce02cb7aea25c2a0b764359d8e20010702e" +"checksum prost-derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e7dc378b94ac374644181a2247cebf59a6ec1c88b49ac77f3a94b86b79d0e11" +"checksum prost-types 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1de482a366941c8d56d19b650fac09ca08508f2a696119ee7513ad590c8bac6f" +"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" +"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0b2f0808e7d7e4fb1cb07feb6ff2f4bc827938f24f8c2e6a3beb7370af544bdd" +"checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48" +"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +"checksum ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c" +"checksum route-recognizer 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3255338088df8146ba63d60a9b8e3556f1146ce2973bc05a75181a42ce2256" +"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" +"checksum schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f6abf258d99c3c1c5c2131d99d064e94b7b3dd5f416483057f308fea253339" +"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" +"checksum seccomp-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e5bcf74ba0708aeaf8d702e4f84f7458ae1de42d80e2c20963a395b6038e6be6" +"checksum security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee63d0f4a9ec776eeb30e220f0bc1e092c3ad744b2a379e3993070364d3adc2" +"checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56" +"checksum select-rustc 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fdb2e15b35a72de428af0da95ba4c990a336602576906f537923c5aa4d695835" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "32746bf0f26eab52f06af0d0aa1984f641341d06d8d673c693871da2d188c9be" +"checksum serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "46a3223d0c9ba936b61c0d2e3e559e3217dbfb8d65d06d26e8b3c25de38bae3e" +"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" +"checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582" +"checksum signal-hook 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "72ab58f1fda436857e6337dcb6a5aaa34f16c5ddc87b3a8b6ef7a212f90b9c5a" +"checksum signal-hook-registry 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cded4ffa32146722ec54ab1f16320568465aa922aa9ab4708129599740da85d7" +"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +"checksum slog 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e1a2eec401952cd7b12a84ea120e2d57281329940c3f93c2bf04f462539508e" +"checksum slog-async 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e544d16c6b230d84c866662fe55e31aacfca6ae71e6fc49ae9a311cb379bfc2f" +"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" +"checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" +"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +"checksum string 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0bbfb8937e38e34c3444ff00afb28b0811d9554f15c5ad64d12b0308d1d1995" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum subtle 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "01dca13cf6c3b179864ab3292bd794e757618d35a7766b7c46050c614ba00829" +"checksum syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)" = "8b4f551a91e2e3848aeef8751d0d4eec9489b6474c720fd4c55958d8d31a430c" +"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" +"checksum take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +"checksum tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7dc4738f2e68ed2855de5ac9cdbe05c9216773ecde4739b2f095002ab03a13ef" +"checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" +"checksum termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a8fb22f7cde82c8220e5aeacb3258ed7ce996142c77cba193f203515e26c330" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum tk-listen 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5462b0f968c0457efe38fcd2df7e487096b992419e4f5337b06775a614bbda4b" +"checksum tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "ec2ffcf4bcfc641413fa0f1427bf8f91dfc78f56a6559cbf50e04837ae442a87" +"checksum tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" +"checksum tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c501eceaf96f0e1793cf26beb63da3d11c738c4a943fdf3746d81d64684c39f" +"checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" +"checksum tokio-executor 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "83ea44c6c0773cc034771693711c35c677b4b5a4b21b9e7071704c54de7d555e" +"checksum tokio-fs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe6dc22b08d6993916647d108a1a7d15b9cd29c4f4496c62b92c45b5041b7af" +"checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" +"checksum tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6af16bfac7e112bea8b0442542161bfc41cbfa4466b580bdda7d18cb88b911ce" +"checksum tokio-signal 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "dd6dc5276ea05ce379a16de90083ec80836440d5ef8a6a39545a3207373b8296" +"checksum tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2162248ff317e2bc713b261f242b69dbb838b85248ed20bb21df56d60ea4cae7" +"checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" +"checksum tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72558af20be886ea124595ea0f806dd5703b8958e4705429dd58b3d8231f72f2" +"checksum tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" +"checksum tokio-trace-core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9c8a256d6956f7cb5e2bdfe8b1e8022f1a09206c6c2b1ba00f3b746b260c613" +"checksum tokio-udp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "66268575b80f4a4a710ef83d087fdfeeabdce9b74c797535fbac18a2cb906e92" +"checksum tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "037ffc3ba0e12a0ab4aca92e5234e0dedeb48fddf6ccd260f1f150a36a9f2445" +"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" +"checksum try_future 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "30454186ee38f29e06f386c6e9f773b7c33e85430db6f90e4597419b4c5baad7" +"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" +"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" +"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unindent 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "834b4441326c660336850c5c0926cc20548e848967a5f57bc20c2b741c8d41f4" +"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" +"checksum utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde" +"checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "797464475f30ddb8830cc529aaaae648d581f99e2036a928877dfde027ddf6b3" +"checksum which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b57acb10231b9493c8472b20cb57317d0679a49e0bdbee44b3b803a6473af164" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +"checksum x25519-dalek 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7ee1585dc1484373cbc1cee7aafda26634665cf449436fd6e24bfd1fad230538" +"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" diff --git a/service/Cargo.toml b/service/Cargo.toml new file mode 100644 index 0000000..f10e77f --- /dev/null +++ b/service/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = ["kbupd", "kbupd_api", "kbupd_api_client", "kbupd_client", "kbupd_config", "kbupd_util", "kbuptlsd", "rustunnel"] + +[patch.crates-io] +openssl = { rev = "ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0", git = "https://github.com/geogriff-signal/rust-openssl.git" } +openssl-sys = { rev = "ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0", git = "https://github.com/geogriff-signal/rust-openssl.git" } \ No newline at end of file diff --git a/service/Makefile b/service/Makefile new file mode 100644 index 0000000..d399d9a --- /dev/null +++ b/service/Makefile @@ -0,0 +1,120 @@ +enclave_includedir = ../enclave/include +enclave_libdir = ../enclave/lib +builddir = build +targetdir = $(builddir)/target + +CARGO ?= cargo +BINDGEN ?= bindgen +DOCKER ?= docker +INSTALL ?= install + +VERSION = $(shell cargo pkgid --package kbupd | cut -d\# -f2) + +INSTALL_PROGRAM = $(INSTALL) -m 755 $(INSTALL_PROGRAM_FLAGS) +INSTALL_DATA = $(INSTALL) -m 644 + +DESTDIR ?= + +export CARGO_TARGET_DIR = $(CURDIR)/$(targetdir) +export CARGO_HOME = $(CURDIR)/$(builddir)/cargo + +## +## targets +## + +.PHONY: default all all-debug kbupd kbupd-debug kbupd-config kbupd-config-debug kbupctl kbupctl-debug kbuptlsd kbuptlsd-debug doc check test clippy bindgen distclean clean docker tar +.PHONY: FORCE + +.SUFFIXES: +.SUFFIXES: .c .o + +default: docker tar + +all: kbupd kbupd-config kbuptlsd + +all-debug: kbupd-debug kbupd-config-debug kbuptlsd-debug + +kbupd: + $(CARGO) build --package=kbupd --bins --release + +kbupd-debug: + $(CARGO) build --package=kbupd --bins + +kbupd-config: + $(CARGO) build --package=kbupd_config --bins --release + +kbupd-config-debug: + $(CARGO) build --package=kbupd_config --bins + +kbupctl: + $(CARGO) build --package=kbupd --bin=kbupctl --release + +kbupctl-debug: + $(CARGO) build --package=kbupd --bin=kbupctl + +kbuptlsd: + $(CARGO) build --package=kbuptlsd --bins --release + +kbuptlsd-debug: + $(CARGO) build --package=kbuptlsd --bins + +doc: + $(CARGO) doc --package=kbupd --release --document-private-items --lib + +check: + $(CARGO) check --package=kbupd --lib --bins --tests + +test: + RUST_BACKTRACE=1 $(CARGO) test --all --exclude=kbuptlsd + $(CARGO) test --package=kbuptlsd + +clippy: + $(CARGO) clippy --package=kbupd + +bindgen: + $(BINDGEN) -o kbupd/src/enclave/ffi/bindgen_wrapper.rs \ + --rust-target 1.33 --with-derive-default --with-derive-eq --no-prepend-enum-name \ + kbupd/src/enclave/ffi/bindgen_wrapper.h -- -I../enclave/include + +distclean: clean + rm -r $(builddir)/ + +clean: + -rm -r $(targetdir)/release/ \ + $(targetdir)/debug/ \ + $(builddir)/tar/ + -rm $(builddir)/*.tar.gz + -$(CARGO) clean --release + +## Docker build env + +MAKETARGET ?= all + +docker: DOCKER_EXTRA=$(shell [ -L build ] && P=$$(readlink build) && echo -v $$P/:$$P ) +docker: + $(DOCKER) build --build-arg UID=$$(id -u) --build-arg GID=$$(id -g) \ + -t kbupd-builder ./docker + $(DOCKER) run --rm --user $$(id -u):$$(id -g) \ + --env "MAKEFLAGS=$(MAKEFLAGS)" \ + -v `pwd`/:/home/rust/src $(DOCKER_EXTRA) kbupd-builder \ + sh -c "cd src; make $(MAKETARGET)" + +## tar package + +tar: $(builddir)/kbupd-$(VERSION)-bin-staging.tar.gz $(builddir)/kbupd-$(VERSION)-bin-production.tar.gz + +$(builddir)/kbupd-$(VERSION)-bin-%.tar.gz: FORCE + $(targetdir)/release/kbupd-config validate frontend config/frontend.$*.yml + $(targetdir)/release/kbupd-config validate replica $(wildcard config/replica-*.$*.yml) + -rm -rf $(builddir)/tar/ + mkdir -p $(builddir)/tar/enclave/ + $(INSTALL_PROGRAM) $(targetdir)/release/kbupd $(builddir)/tar/ + $(INSTALL_PROGRAM) $(targetdir)/release/kbupd-config $(builddir)/tar/ + $(INSTALL_PROGRAM) $(targetdir)/release/kbupctl $(builddir)/tar/ + $(INSTALL_PROGRAM) $(targetdir)/release/kbuptlsd $(builddir)/tar/ + $(INSTALL_DATA) $(wildcard kbupd/res/enclave/*.so) $(builddir)/tar/enclave/ + $(INSTALL_DATA) config/frontend.$*.yml $(builddir)/tar/ + $(INSTALL_DATA) $(wildcard config/replica-*.$*.yml) $(builddir)/tar/ + $(INSTALL_DATA) config/kbuptlsd.intel-client.$*.yml $(builddir)/tar/ + $(INSTALL_DATA) config/peer_ca_cert.$*.pem $(builddir)/tar/ + tar -czf $(builddir)/kbupd-$(VERSION)-bin-$*.tar.gz -C $(builddir)/tar . diff --git a/service/ci/azure-pipelines/jobs/client_test.yml b/service/ci/azure-pipelines/jobs/client_test.yml new file mode 100644 index 0000000..c15ea79 --- /dev/null +++ b/service/ci/azure-pipelines/jobs/client_test.yml @@ -0,0 +1,51 @@ +# +# Azure Pipelines steps to run service/test/client_test.py +# + +jobs: +- job: client_test + displayName: client_test + pool: client_test + steps: + - checkout: none + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: kbupd + targetPath: . + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: kbupctl + targetPath: . + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: kbuptlsd + targetPath: . + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: client_test + targetPath: . + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: client_test_config + targetPath: . + - task: DownloadSecureFile@1 + name: ias_tls_config + inputs: + secureFile: kbuptlsd.intel-client.client_test.yml + - task: DownloadSecureFile@1 + name: kbupd_enclave + inputs: + secureFile: enclave-f51f435802ada769e67aaf5744372bb7e7d519eecf996d335eb5b46b872b5789-test.so + + - script: | + chmod +x kbupd kbupctl kbuptlsd + displayName: set up client_test + + - script: python3 client_test.py + displayName: client_test.py + timeoutInMinutes: 30 + env: + IAS_TLS_CONFIG: $(ias_tls_config.secureFilePath) + IAS_SPID: $(IAS_SPID) + ENCLAVE_PATH: $(kbupd_enclave.secureFilePath) + ENCLAVE_DEBUG: no diff --git a/service/ci/azure-pipelines/jobs/docker_build_kbupd_builder.yml b/service/ci/azure-pipelines/jobs/docker_build_kbupd_builder.yml new file mode 100644 index 0000000..c01c02a --- /dev/null +++ b/service/ci/azure-pipelines/jobs/docker_build_kbupd_builder.yml @@ -0,0 +1,34 @@ +# +# Azure Pipelines job to build the kbupd-builder docker image used to build backup service binaries inside. +# + +jobs: +- job: docker_build_kbupd_builder + displayName: docker build kbupd-builder + pool: + vmImage: ubuntu-18.04 + steps: + - task: Docker@2 + displayName: docker login + inputs: + command: login + containerRegistry: signalbackupci-container-registry + + - script: docker pull signalbackupci.azurecr.io/signalbackupci:kbupd-builder || true + displayName: docker pull + + - task: Docker@2 + displayName: docker build + inputs: + command: build + dockerfile: service/docker/Dockerfile + arguments: --build-arg UID=1000 --build-arg GID=1000 --cache-from signalbackupci.azurecr.io/signalbackupci:kbupd-builder + repository: signalbackupci + tags: kbupd-builder + + - task: Docker@2 + displayName: docker push + inputs: + command: push + repository: signalbackupci + tags: kbupd-builder diff --git a/service/ci/azure-pipelines/jobs/make_all.yml b/service/ci/azure-pipelines/jobs/make_all.yml new file mode 100644 index 0000000..70e0b6d --- /dev/null +++ b/service/ci/azure-pipelines/jobs/make_all.yml @@ -0,0 +1,24 @@ +# +# Azure Pipelines job to build all service binaries from within in the kbupd-builder docker image. +# + +jobs: +- job: make_all + displayName: make all + pool: + vmImage: ubuntu-18.04 + container: kbupd-builder + steps: + - script: make -C service/ all + displayName: make all + + - publish: service/build/target/release/kbupd + artifact: kbupd + - publish: service/build/target/release/kbupctl + artifact: kbupctl + - publish: service/build/target/release/kbuptlsd + artifact: kbuptlsd + - publish: service/test/ + artifact: client_test + - publish: service/config/ + artifact: client_test_config diff --git a/service/ci/azure-pipelines/jobs/make_test.yml b/service/ci/azure-pipelines/jobs/make_test.yml new file mode 100644 index 0000000..e1eb0e6 --- /dev/null +++ b/service/ci/azure-pipelines/jobs/make_test.yml @@ -0,0 +1,20 @@ +# +# Azure Pipelines job to run all service tests from within in the kbupd-builder docker image. +# + +jobs: +- job: make_test + displayName: make test + pool: + vmImage: ubuntu-18.04 + container: kbupd-builder + steps: + - script: make -C service/ all-debug test + displayName: make all-debug test + + - publish: service/build/target/debug/kbupd + artifact: kbupd-debug + - publish: service/build/target/debug/kbupctl + artifact: kbupctl-debug + - publish: service/build/target/debug/kbuptlsd + artifact: kbuptlsd-debug diff --git a/service/ci/azure-pipelines/master.yml b/service/ci/azure-pipelines/master.yml new file mode 100644 index 0000000..18602b2 --- /dev/null +++ b/service/ci/azure-pipelines/master.yml @@ -0,0 +1,47 @@ +# +# Azure Pipeline to build & test backup service binaries. Triggers on commits and PRs to master. +# + +pr: + branches: + include: + - master + +trigger: + branches: + include: + - master + +variables: + - group: client_test + +resources: + containers: + - container: kbupd-builder + image: signalbackupci:kbupd-builder + endpoint: signalbackupci-container-registry + options: --user 0:0 + +stages: +- stage: docker_build_kbupd_builder + displayName: docker build kbupd-builder + jobs: + - template: jobs/docker_build_kbupd_builder.yml + +- stage: make_test + displayName: make test + dependsOn: docker_build_kbupd_builder + jobs: + - template: jobs/make_test.yml + +- stage: make_all + displayName: make all + dependsOn: docker_build_kbupd_builder + jobs: + - template: jobs/make_all.yml + +- stage: client_test + displayName: client_test + dependsOn: make_all + jobs: + - template: jobs/client_test.yml diff --git a/service/config/frontend.benchmark.yml b/service/config/frontend.benchmark.yml new file mode 100644 index 0000000..50dfae8 --- /dev/null +++ b/service/config/frontend.benchmark.yml @@ -0,0 +1,25 @@ +api: + listenHostPort: 127.0.0.1:8000 + userAuthenticationTokenSharedSecret: 0000000000000000000000000000000000000000000000000000000000000000 + backupIdSecret: 0000000000000000000000000000000000000000000000000000000000000000 + +attestation: + host: https://test-as.sgx.trustedservices.intel.com/ + spid: 00000000000000000000000000000000 + tlsConfigPath: "" + acceptGroupOutOfDate: true + disabled: true + +control: + listenHostPort: 127.0.0.1:1337 + +enclaves: + - name: test + mrenclave: libkbupd_enclave.hardened.debug + debug: true + electionTimeoutMs: 5000 + + pendingRequestCount: 32768 + pendingRequestTtlMs: 0 + + partitions: [] diff --git a/service/config/frontend.client_test.yml b/service/config/frontend.client_test.yml new file mode 100644 index 0000000..50dfae8 --- /dev/null +++ b/service/config/frontend.client_test.yml @@ -0,0 +1,25 @@ +api: + listenHostPort: 127.0.0.1:8000 + userAuthenticationTokenSharedSecret: 0000000000000000000000000000000000000000000000000000000000000000 + backupIdSecret: 0000000000000000000000000000000000000000000000000000000000000000 + +attestation: + host: https://test-as.sgx.trustedservices.intel.com/ + spid: 00000000000000000000000000000000 + tlsConfigPath: "" + acceptGroupOutOfDate: true + disabled: true + +control: + listenHostPort: 127.0.0.1:1337 + +enclaves: + - name: test + mrenclave: libkbupd_enclave.hardened.debug + debug: true + electionTimeoutMs: 5000 + + pendingRequestCount: 32768 + pendingRequestTtlMs: 0 + + partitions: [] diff --git a/service/config/frontend.sample.yml b/service/config/frontend.sample.yml new file mode 100644 index 0000000..4adfd09 --- /dev/null +++ b/service/config/frontend.sample.yml @@ -0,0 +1,44 @@ +api: + listenHostPort: # host:port to listen on for the HTTP API + userAuthenticationTokenSharedSecret: # hex-encoded secret shared with Signal-Server used to generate auth tokens for Signal users + backupIdSecret: # hex-encoded secret used to calculate Backup IDs assigned to Signal users + limits: + token: # Rate limits for token requests + bucketSize: # Leaky bucket size + leakRatePerMinute: # Leaky bucket rate per minute + attestation: # Rate limits for attestation requests + bucketSize: # Leaky bucket size + leakRatePerMinute: # Leaky bucket rate per minute + backup: # Rate limits for key backup requests + bucketSize: # Leaky bucket size + leakRatePerMinute: # Leaky bucket rate per minute + +attestation: # IAS (Intel Attestation Services) configuration + host: # Intel Attestation Services (IAS) host + tlsConfigPath: # kbuptlsd configuration file path to use connecting to IAS, relative to the directory containing this file + spid: # hex-encoded 16-byte IAS assigned Service Provider ID + acceptGroupOutOfDate: # Whether to serve IAS responses with GROUP_OUT_OF_DATE instead of OK [true|false] + +control: + listenHostPort: # host:port to listen on for the kbupctl API + +metrics: + reporters: + - type: json # JsonMetricsReporter config + hostname: # hostname of metrics server to upload to + token: # auth token to send to metrics server via the "t" query param + intervalSeconds: # interval to upload metrics, in seconds (default 60) + +enclaves: # list of enclave instances + - name: # enclave instance name used in the HTTP API + mrenclave: # hex-encoded mrenclave value + debug: # Whether this is a debug or production enclave [true|false] + electionTimeoutMs: # replica raft group election timeout in milliseconds + pendingRequestCount: # maximum number of pending client requests, per partition + pendingRequestTtlMs: # minimum ttl of a pending client request before it may be purged to make room for others, in milliseconds + partitions: # list of Key Backup partitions and their replicas + - range: # optional partition Key Backup ID range that this partition serves + firstBackupId: # hex-encoded 32-byte Key Backup ID inclusive lower bound + lastBackupId: # hex-encoded 32-byte Key Backup ID inclusive upper bound + replicas: # list of replicas serving this Key Backup partition + - hostPort: # host:port to connect to this replica diff --git a/service/config/kbuptlsd.sample.yml b/service/config/kbuptlsd.sample.yml new file mode 100644 index 0000000..60b6a48 --- /dev/null +++ b/service/config/kbuptlsd.sample.yml @@ -0,0 +1,13 @@ +server: + listenHostPort: # host:port to listen on for TLS connections + targetHostPort: # host:port to proxy connections to + caCertificatePem: # PEM-encoded CA certificate to authenticate clients against + +client: + clientCertificatePkcs12: # (optional) base64-encoded DER PKCS12 client certificate and key + caCertificates: # Root CA certificates to trust when validating server certificate + - system # Trust all certificates in /etc/ssl/certs/ + - customPem: # PEM-encoded CA certificate to trust + hostnameValidation: # Options for validating server certificate hostname + acceptInvalid # Do not validate the server certificate hostname + hostname: # Hostname to require in the server certificate diff --git a/service/config/replica.benchmark.yml b/service/config/replica.benchmark.yml new file mode 100644 index 0000000..9247b8f --- /dev/null +++ b/service/config/replica.benchmark.yml @@ -0,0 +1,23 @@ +attestation: + host: https://test-as.sgx.trustedservices.intel.com/ + spid: 00000000000000000000000000000000 + tlsConfigPath: "" + acceptGroupOutOfDate: true + disabled: true + +control: + listenHostPort: 127.0.0.1:31338 + +enclave: + listenHostPort: 127.0.0.1:31337 + maxConnections: 128 + mrenclave: libkbupd_enclave.hardened.debug + debug: true + storageSize: 2000000 + raftLogSize: 104857600 + electionTimeoutMs: 5000 + electionHeartbeats: 10 + replicationChunkSize: 266240 + transferChunkSize: 266240 + attestationExpiryCommitIntervalMs: 6000000 + maxFrontendCount: 128 diff --git a/service/config/replica.client_test.yml b/service/config/replica.client_test.yml new file mode 100644 index 0000000..e0b054c --- /dev/null +++ b/service/config/replica.client_test.yml @@ -0,0 +1,23 @@ +attestation: + host: https://test-as.sgx.trustedservices.intel.com/ + spid: 00000000000000000000000000000000 + tlsConfigPath: "" + acceptGroupOutOfDate: true + disabled: true + +control: + listenHostPort: 127.0.0.1:31338 + +enclave: + listenHostPort: 127.0.0.1:31337 + maxConnections: 128 + mrenclave: libkbupd_enclave.hardened.debug + debug: true + storageSize: 500000 + raftLogSize: 10485760 + electionTimeoutMs: 5000 + electionHeartbeats: 2 + replicationChunkSize: 0 + transferChunkSize: 0 + attestationExpiryCommitIntervalMs: 60000 + maxFrontendCount: 1 diff --git a/service/config/replica.sample.yml b/service/config/replica.sample.yml new file mode 100644 index 0000000..77d9750 --- /dev/null +++ b/service/config/replica.sample.yml @@ -0,0 +1,36 @@ +attestation: # IAS (Intel Attestation Services) configuration + host: # Intel Attestation Services (IAS) host + tlsConfigPath: # kbuptlsd configuration file path to use connecting to IAS, relative to the directory containing this file + spid: # hex-encoded 16-byte IAS assigned Service Provider ID + acceptGroupOutOfDate: # Whether to serve IAS responses with GROUP_OUT_OF_DATE instead of OK [true|false] + +control: + listenHostPort: # host:port to listen on for the kbupctl API + +metrics: + reporters: + - type: json # JsonMetricsReporter config + hostname: # hostname of metrics server to upload to + token: # auth token to send to metrics server via the "t" query param + intervalSeconds: # interval to upload metrics, in seconds (default 60) + +enclave: + listenHostPort: # host:port to listen on for other replicas + maxConnections: # maximum number of concurrent connections to replicas and frontends + mrenclave: # hex-encoded mrenclave value + debug: # Whether this is a debug or production enclave [true|false] + storageSize: # storage capacity for key backup entry data, in number of backups + raftLogSize: # size allocated in RAM for key backup raft log data, in bytes + electionTimeoutMs: # raft group election timeout in milliseconds + electionHeartbeats: # number of heartbeats per raft group election timeout period (10 recommended) + replicationChunkSize: # target replication chunk size in bytes + transferChunkSize: # target transfer chunk size in bytes + attestationExpiryCommitIntervalMs: # interval at which to commit the latest minimum valid attestation expiration time to this partition + maxFrontendCount: # maximum number of frontend sessions to keep before evicting the least recently used + replicas: # list of host:ports of peer replicas serving this Key Backup partition + - hostPort: # host:port to connect to this replica + sourcePartition: # optional source partition information to transfer data from + firstBackupId: # hex-encoded 32-byte Key Backup ID inclusive lower bound of Backup ID range that this partition serves + lastBackupId: # hex-encoded 32-byte Key Backup ID inclusive upper bound of Backup ID range that this partition serves + replicas: # list of source replicas serving this Key Backup partition + - hostPort: # host:port to connect to this replica diff --git a/service/docker/Dockerfile b/service/docker/Dockerfile new file mode 100644 index 0000000..5e9b2e2 --- /dev/null +++ b/service/docker/Dockerfile @@ -0,0 +1,65 @@ +FROM ubuntu:bionic + +COPY linux-sgx.gpg /tmp/docker/ + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + apt-transport-https \ + build-essential \ + curl \ + gpg-agent \ + libseccomp2 \ + libseccomp-dev \ + libssl-dev \ + pkg-config \ + protobuf-compiler \ + software-properties-common \ + && apt-key add /tmp/docker/linux-sgx.gpg \ + && apt-add-repository "deb https://download.01.org/intel-sgx/sgx_repo/ubuntu bionic main" \ + && apt-get install -y --download-only libsgx-enclave-common libsgx-enclave-common-dev \ + && dpkg --unpack /var/cache/apt/archives/libsgx-enclave-common_*.deb \ + && dpkg --install --ignore-depends=libsgx-enclave-common /var/cache/apt/archives/libsgx-enclave-common-dev_*.deb \ + && rm -rf /var/lib/apt/lists/* + +ARG UID +ARG GID + +#Create a user to map the host user to. +RUN groupadd -o -g "${GID}" rust \ + && useradd -m -o -u "${UID}" -g "${GID}" -s /bin/bash rust \ + && chown -R rust.rust /tmp/docker + +USER rust +ENV HOME /home/rust +ENV USER rust +ENV SHELL /bin/bash + +WORKDIR /home/rust + +ARG TOOLCHAIN=1.39.0 + +COPY rustup-init.sha256 /tmp/docker/ + +RUN curl -f https://static.rust-lang.org/rustup/archive/1.20.2/x86_64-unknown-linux-gnu/rustup-init -o /tmp/rustup-init \ + && [ `sha256sum /tmp/rustup-init|cut -d' ' -f1` = `cut -d' ' -f1. + */ + +extern crate x86; + +struct BenchState { + start: u64, + sum: u64, + count: u64, +} +fn with_bench(fun: F) -> R +where F: for<'a> FnOnce(&'a mut BenchState) -> R { + thread_local! { + static BENCH: std::cell::UnsafeCell = std::cell::UnsafeCell::new(BenchState { start: 0, sum: 0, count: 0 }); + } + BENCH.with(|bench| { + unsafe { fun(&mut *bench.get()) } + }) +} +pub fn bench_reset() { + with_bench(|bench| { + bench.sum = 0; + bench.count = 0; + }); +} +pub fn bench_start() { + with_bench(|bench| bench.start = unsafe { x86::current::time::rdtscp() }); +} +pub fn bench_stop() { + with_bench(|bench| { + if bench.start != 0 { + let end = unsafe { x86::current::time::rdtscp() }; + bench.sum += end - bench.start; + bench.count += 1; + bench.start = 0; + if bench.sum > 10000000000 { + println!("{} laps in {} cycles = {} cycles / lap", bench.count, bench.sum, bench.sum / bench.count); + bench.sum = 0; + bench.count = 0; + } + } + }); +} diff --git a/service/kbupd/build.rs b/service/kbupd/build.rs new file mode 100644 index 0000000..7098072 --- /dev/null +++ b/service/kbupd/build.rs @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +fn main() { + println!("cargo:rustc-link-search=native=./lib"); + println!("cargo:rustc-link-lib=static=kbupd_enclave_u"); + println!("cargo:rustc-link-lib=dylib=sgx_uae_service"); + println!("cargo:rustc-link-lib=dylib=sgx_urts"); + println!("cargo:rustc-link-lib=dylib=sgx_enclave_common"); + + let mut cc = cc::Build::new(); + cc.file("c_src/kbupd_enclave_u.c") + .include("c_src") + .compile("kbupd_enclave_u"); + + let mut protoc = prost_build::Config::new(); + protoc.extern_path(".protobufs.kbupd_client", "kbupd_client") + .compile_protos(&["src/kbupd.proto"], &["src/", "../kbupd_client/src/"]) + .expect("error compiling protobufs"); +} diff --git a/service/kbupd/c_src/kbupd_enclave_u.c b/service/kbupd/c_src/kbupd_enclave_u.c new file mode 100644 index 0000000..d8b0f81 --- /dev/null +++ b/service/kbupd/c_src/kbupd_enclave_u.c @@ -0,0 +1,279 @@ +#include "kbupd_enclave_u.h" +#include + +typedef struct ms_kbupd_enclave_recv_untrusted_msg_t { + const uint8_t* ms_data; + size_t ms_data_size; +} ms_kbupd_enclave_recv_untrusted_msg_t; + +typedef struct ms_sgxsd_enclave_node_init_t { + sgx_status_t ms_retval; + const sgxsd_node_init_args_t* ms_p_args; +} ms_sgxsd_enclave_node_init_t; + +typedef struct ms_sgxsd_enclave_get_next_report_t { + sgx_status_t ms_retval; + sgx_target_info_t ms_qe_target_info; + sgx_report_t* ms_p_report; +} ms_sgxsd_enclave_get_next_report_t; + +typedef struct ms_sgxsd_enclave_set_current_quote_t { + sgx_status_t ms_retval; +} ms_sgxsd_enclave_set_current_quote_t; + +typedef struct ms_sgxsd_enclave_negotiate_request_t { + sgx_status_t ms_retval; + const sgxsd_request_negotiation_request_t* ms_p_request; + sgxsd_request_negotiation_response_t* ms_p_response; +} ms_sgxsd_enclave_negotiate_request_t; + +typedef struct ms_sgxsd_enclave_server_start_t { + sgx_status_t ms_retval; + const sgxsd_server_init_args_t* ms_p_args; + sgxsd_server_state_handle_t ms_state_handle; +} ms_sgxsd_enclave_server_start_t; + +typedef struct ms_sgxsd_enclave_server_call_t { + sgx_status_t ms_retval; + const sgxsd_server_handle_call_args_t* ms_p_args; + const sgxsd_msg_header_t* ms_msg_header; + uint8_t* ms_msg_data; + size_t ms_msg_size; + sgxsd_msg_tag_t ms_msg_tag; + sgxsd_server_state_handle_t ms_state_handle; +} ms_sgxsd_enclave_server_call_t; + +typedef struct ms_sgxsd_enclave_server_stop_t { + sgx_status_t ms_retval; + const sgxsd_server_terminate_args_t* ms_p_args; + sgxsd_server_state_handle_t ms_state_handle; +} ms_sgxsd_enclave_server_stop_t; + +typedef struct ms_kbupd_enclave_ocall_recv_enclave_msg_t { + const uint8_t* ms_data; + size_t ms_data_size; +} ms_kbupd_enclave_ocall_recv_enclave_msg_t; + +typedef struct ms_kbupd_enclave_ocall_alloc_t { + void* ms_retval; + size_t* ms_size; +} ms_kbupd_enclave_ocall_alloc_t; + +typedef struct ms_kbupd_enclave_ocall_panic_t { + const uint8_t* ms_msg; + size_t ms_msg_size; +} ms_kbupd_enclave_ocall_panic_t; + +typedef struct ms_sgxsd_ocall_reply_t { + sgx_status_t ms_retval; + const sgxsd_msg_header_t* ms_reply_header; + const uint8_t* ms_reply_data; + size_t ms_reply_data_size; + sgxsd_msg_tag_t ms_msg_tag; +} ms_sgxsd_ocall_reply_t; + +typedef struct ms_sgx_oc_cpuidex_t { + int* ms_cpuinfo; + int ms_leaf; + int ms_subleaf; +} ms_sgx_oc_cpuidex_t; + +typedef struct ms_sgx_thread_wait_untrusted_event_ocall_t { + int ms_retval; + const void* ms_self; +} ms_sgx_thread_wait_untrusted_event_ocall_t; + +typedef struct ms_sgx_thread_set_untrusted_event_ocall_t { + int ms_retval; + const void* ms_waiter; +} ms_sgx_thread_set_untrusted_event_ocall_t; + +typedef struct ms_sgx_thread_setwait_untrusted_events_ocall_t { + int ms_retval; + const void* ms_waiter; + const void* ms_self; +} ms_sgx_thread_setwait_untrusted_events_ocall_t; + +typedef struct ms_sgx_thread_set_multiple_untrusted_events_ocall_t { + int ms_retval; + const void** ms_waiters; + size_t ms_total; +} ms_sgx_thread_set_multiple_untrusted_events_ocall_t; + +static sgx_status_t SGX_CDECL kbupd_enclave_kbupd_enclave_ocall_recv_enclave_msg(void* pms) +{ + ms_kbupd_enclave_ocall_recv_enclave_msg_t* ms = SGX_CAST(ms_kbupd_enclave_ocall_recv_enclave_msg_t*, pms); + kbupd_enclave_ocall_recv_enclave_msg(ms->ms_data, ms->ms_data_size); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_kbupd_enclave_ocall_alloc(void* pms) +{ + ms_kbupd_enclave_ocall_alloc_t* ms = SGX_CAST(ms_kbupd_enclave_ocall_alloc_t*, pms); + ms->ms_retval = kbupd_enclave_ocall_alloc(ms->ms_size); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_kbupd_enclave_ocall_panic(void* pms) +{ + ms_kbupd_enclave_ocall_panic_t* ms = SGX_CAST(ms_kbupd_enclave_ocall_panic_t*, pms); + kbupd_enclave_ocall_panic(ms->ms_msg, ms->ms_msg_size); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_sgxsd_ocall_reply(void* pms) +{ + ms_sgxsd_ocall_reply_t* ms = SGX_CAST(ms_sgxsd_ocall_reply_t*, pms); + ms->ms_retval = sgxsd_ocall_reply(ms->ms_reply_header, ms->ms_reply_data, ms->ms_reply_data_size, ms->ms_msg_tag); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_sgx_oc_cpuidex(void* pms) +{ + ms_sgx_oc_cpuidex_t* ms = SGX_CAST(ms_sgx_oc_cpuidex_t*, pms); + sgx_oc_cpuidex(ms->ms_cpuinfo, ms->ms_leaf, ms->ms_subleaf); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_sgx_thread_wait_untrusted_event_ocall(void* pms) +{ + ms_sgx_thread_wait_untrusted_event_ocall_t* ms = SGX_CAST(ms_sgx_thread_wait_untrusted_event_ocall_t*, pms); + ms->ms_retval = sgx_thread_wait_untrusted_event_ocall(ms->ms_self); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_sgx_thread_set_untrusted_event_ocall(void* pms) +{ + ms_sgx_thread_set_untrusted_event_ocall_t* ms = SGX_CAST(ms_sgx_thread_set_untrusted_event_ocall_t*, pms); + ms->ms_retval = sgx_thread_set_untrusted_event_ocall(ms->ms_waiter); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_sgx_thread_setwait_untrusted_events_ocall(void* pms) +{ + ms_sgx_thread_setwait_untrusted_events_ocall_t* ms = SGX_CAST(ms_sgx_thread_setwait_untrusted_events_ocall_t*, pms); + ms->ms_retval = sgx_thread_setwait_untrusted_events_ocall(ms->ms_waiter, ms->ms_self); + + return SGX_SUCCESS; +} + +static sgx_status_t SGX_CDECL kbupd_enclave_sgx_thread_set_multiple_untrusted_events_ocall(void* pms) +{ + ms_sgx_thread_set_multiple_untrusted_events_ocall_t* ms = SGX_CAST(ms_sgx_thread_set_multiple_untrusted_events_ocall_t*, pms); + ms->ms_retval = sgx_thread_set_multiple_untrusted_events_ocall(ms->ms_waiters, ms->ms_total); + + return SGX_SUCCESS; +} + +static const struct { + size_t nr_ocall; + void * table[9]; +} ocall_table_kbupd_enclave = { + 9, + { + (void*)kbupd_enclave_kbupd_enclave_ocall_recv_enclave_msg, + (void*)kbupd_enclave_kbupd_enclave_ocall_alloc, + (void*)kbupd_enclave_kbupd_enclave_ocall_panic, + (void*)kbupd_enclave_sgxsd_ocall_reply, + (void*)kbupd_enclave_sgx_oc_cpuidex, + (void*)kbupd_enclave_sgx_thread_wait_untrusted_event_ocall, + (void*)kbupd_enclave_sgx_thread_set_untrusted_event_ocall, + (void*)kbupd_enclave_sgx_thread_setwait_untrusted_events_ocall, + (void*)kbupd_enclave_sgx_thread_set_multiple_untrusted_events_ocall, + } +}; +sgx_status_t kbupd_enclave_recv_untrusted_msg(sgx_enclave_id_t eid, const uint8_t* data, size_t data_size) +{ + sgx_status_t status; + ms_kbupd_enclave_recv_untrusted_msg_t ms; + ms.ms_data = data; + ms.ms_data_size = data_size; + status = sgx_ecall(eid, 0, &ocall_table_kbupd_enclave, &ms); + return status; +} + +sgx_status_t sgxsd_enclave_node_init(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_node_init_args_t* p_args) +{ + sgx_status_t status; + ms_sgxsd_enclave_node_init_t ms; + ms.ms_p_args = p_args; + status = sgx_ecall(eid, 1, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgxsd_enclave_get_next_report(sgx_enclave_id_t eid, sgx_status_t* retval, sgx_target_info_t qe_target_info, sgx_report_t* p_report) +{ + sgx_status_t status; + ms_sgxsd_enclave_get_next_report_t ms; + ms.ms_qe_target_info = qe_target_info; + ms.ms_p_report = p_report; + status = sgx_ecall(eid, 2, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgxsd_enclave_set_current_quote(sgx_enclave_id_t eid, sgx_status_t* retval) +{ + sgx_status_t status; + ms_sgxsd_enclave_set_current_quote_t ms; + status = sgx_ecall(eid, 3, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgxsd_enclave_negotiate_request(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_request_negotiation_request_t* p_request, sgxsd_request_negotiation_response_t* p_response) +{ + sgx_status_t status; + ms_sgxsd_enclave_negotiate_request_t ms; + ms.ms_p_request = p_request; + ms.ms_p_response = p_response; + status = sgx_ecall(eid, 4, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgxsd_enclave_server_start(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_server_init_args_t* p_args, sgxsd_server_state_handle_t state_handle) +{ + sgx_status_t status; + ms_sgxsd_enclave_server_start_t ms; + ms.ms_p_args = p_args; + ms.ms_state_handle = state_handle; + status = sgx_ecall(eid, 5, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgxsd_enclave_server_call(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_server_handle_call_args_t* p_args, const sgxsd_msg_header_t* msg_header, uint8_t* msg_data, size_t msg_size, sgxsd_msg_tag_t msg_tag, sgxsd_server_state_handle_t state_handle) +{ + sgx_status_t status; + ms_sgxsd_enclave_server_call_t ms; + ms.ms_p_args = p_args; + ms.ms_msg_header = msg_header; + ms.ms_msg_data = msg_data; + ms.ms_msg_size = msg_size; + ms.ms_msg_tag = msg_tag; + ms.ms_state_handle = state_handle; + status = sgx_ecall(eid, 6, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + +sgx_status_t sgxsd_enclave_server_stop(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_server_terminate_args_t* p_args, sgxsd_server_state_handle_t state_handle) +{ + sgx_status_t status; + ms_sgxsd_enclave_server_stop_t ms; + ms.ms_p_args = p_args; + ms.ms_state_handle = state_handle; + status = sgx_ecall(eid, 7, &ocall_table_kbupd_enclave, &ms); + if (status == SGX_SUCCESS && retval) *retval = ms.ms_retval; + return status; +} + diff --git a/service/kbupd/c_src/kbupd_enclave_u.h b/service/kbupd/c_src/kbupd_enclave_u.h new file mode 100644 index 0000000..bc1191c --- /dev/null +++ b/service/kbupd/c_src/kbupd_enclave_u.h @@ -0,0 +1,74 @@ +#ifndef KBUPD_ENCLAVE_U_H__ +#define KBUPD_ENCLAVE_U_H__ + +#include +#include +#include +#include +#include "sgx_edger8r.h" /* for sgx_status_t etc. */ + +#include "kbupd_sgxsd_callbacks.h" +#include "stdbool.h" +#include "sgx_quote.h" +#include "sgx_report.h" +#include "sgxsd.h" + +#include /* for size_t */ + +#define SGX_CAST(type, item) ((type)(item)) + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef KBUPD_ENCLAVE_OCALL_RECV_ENCLAVE_MSG_DEFINED__ +#define KBUPD_ENCLAVE_OCALL_RECV_ENCLAVE_MSG_DEFINED__ +void SGX_UBRIDGE(SGX_NOCONVENTION, kbupd_enclave_ocall_recv_enclave_msg, (const uint8_t* data, size_t data_size)); +#endif +#ifndef KBUPD_ENCLAVE_OCALL_ALLOC_DEFINED__ +#define KBUPD_ENCLAVE_OCALL_ALLOC_DEFINED__ +void* SGX_UBRIDGE(SGX_NOCONVENTION, kbupd_enclave_ocall_alloc, (size_t* size)); +#endif +#ifndef KBUPD_ENCLAVE_OCALL_PANIC_DEFINED__ +#define KBUPD_ENCLAVE_OCALL_PANIC_DEFINED__ +void SGX_UBRIDGE(SGX_NOCONVENTION, kbupd_enclave_ocall_panic, (const uint8_t* msg, size_t msg_size)); +#endif +#ifndef SGXSD_OCALL_REPLY_DEFINED__ +#define SGXSD_OCALL_REPLY_DEFINED__ +sgx_status_t SGX_UBRIDGE(SGX_NOCONVENTION, sgxsd_ocall_reply, (const sgxsd_msg_header_t* reply_header, const uint8_t* reply_data, size_t reply_data_size, sgxsd_msg_tag_t msg_tag)); +#endif +#ifndef SGX_OC_CPUIDEX_DEFINED__ +#define SGX_OC_CPUIDEX_DEFINED__ +void SGX_UBRIDGE(SGX_CDECL, sgx_oc_cpuidex, (int cpuinfo[4], int leaf, int subleaf)); +#endif +#ifndef SGX_THREAD_WAIT_UNTRUSTED_EVENT_OCALL_DEFINED__ +#define SGX_THREAD_WAIT_UNTRUSTED_EVENT_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, sgx_thread_wait_untrusted_event_ocall, (const void* self)); +#endif +#ifndef SGX_THREAD_SET_UNTRUSTED_EVENT_OCALL_DEFINED__ +#define SGX_THREAD_SET_UNTRUSTED_EVENT_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, sgx_thread_set_untrusted_event_ocall, (const void* waiter)); +#endif +#ifndef SGX_THREAD_SETWAIT_UNTRUSTED_EVENTS_OCALL_DEFINED__ +#define SGX_THREAD_SETWAIT_UNTRUSTED_EVENTS_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, sgx_thread_setwait_untrusted_events_ocall, (const void* waiter, const void* self)); +#endif +#ifndef SGX_THREAD_SET_MULTIPLE_UNTRUSTED_EVENTS_OCALL_DEFINED__ +#define SGX_THREAD_SET_MULTIPLE_UNTRUSTED_EVENTS_OCALL_DEFINED__ +int SGX_UBRIDGE(SGX_CDECL, sgx_thread_set_multiple_untrusted_events_ocall, (const void** waiters, size_t total)); +#endif + +sgx_status_t kbupd_enclave_recv_untrusted_msg(sgx_enclave_id_t eid, const uint8_t* data, size_t data_size); +sgx_status_t sgxsd_enclave_node_init(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_node_init_args_t* p_args); +sgx_status_t sgxsd_enclave_get_next_report(sgx_enclave_id_t eid, sgx_status_t* retval, sgx_target_info_t qe_target_info, sgx_report_t* p_report); +sgx_status_t sgxsd_enclave_set_current_quote(sgx_enclave_id_t eid, sgx_status_t* retval); +sgx_status_t sgxsd_enclave_negotiate_request(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_request_negotiation_request_t* p_request, sgxsd_request_negotiation_response_t* p_response); +sgx_status_t sgxsd_enclave_server_start(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_server_init_args_t* p_args, sgxsd_server_state_handle_t state_handle); +sgx_status_t sgxsd_enclave_server_call(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_server_handle_call_args_t* p_args, const sgxsd_msg_header_t* msg_header, uint8_t* msg_data, size_t msg_size, sgxsd_msg_tag_t msg_tag, sgxsd_server_state_handle_t state_handle); +sgx_status_t sgxsd_enclave_server_stop(sgx_enclave_id_t eid, sgx_status_t* retval, const sgxsd_server_terminate_args_t* p_args, sgxsd_server_state_handle_t state_handle); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/service/kbupd/c_src/kbupd_sgxsd_callbacks.h b/service/kbupd/c_src/kbupd_sgxsd_callbacks.h new file mode 100644 index 0000000..5fcd674 --- /dev/null +++ b/service/kbupd/c_src/kbupd_sgxsd_callbacks.h @@ -0,0 +1,31 @@ +#ifndef _KBUPD_SGXSD_CALLBACKS_H +#define _KBUPD_SGXSD_CALLBACKS_H + +#include + +typedef enum kbupd_request_type { + KBUPD_REQUEST_TYPE_ANY = 0, + KBUPD_REQUEST_TYPE_BACKUP = 1, + KBUPD_REQUEST_TYPE_RESTORE = 2, + KBUPD_REQUEST_TYPE_DELETE = 3, +} kbupd_request_type_t; + +typedef struct sgxsd_server_init_args { +} sgxsd_server_init_args_t; +_Static_assert(sizeof(sgxsd_server_init_args_t) == 0, "Enclave ABI compatibility"); + +typedef struct sgxsd_server_handle_call_args { + uint8_t backup_id[32]; + uint32_t request_type; +} sgxsd_server_handle_call_args_t; +_Static_assert(sizeof(sgxsd_server_handle_call_args_t) == 36, "Enclave ABI compatibility"); + +typedef struct sgxsd_server_terminate_args { +} sgxsd_server_terminate_args_t; +_Static_assert(sizeof(sgxsd_server_terminate_args_t) == 0, "Enclave ABI compatibility"); + +typedef struct sgxsd_ra_get_quote_args { + const void *args; +} sgxsd_ra_get_quote_args_t; + +#endif diff --git a/service/kbupd/c_src/sgxsd.h b/service/kbupd/c_src/sgxsd.h new file mode 100644 index 0000000..536ee74 --- /dev/null +++ b/service/kbupd/c_src/sgxsd.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2017 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @author Jeff Griffin + */ +#ifndef _SGXSD_H +#define _SGXSD_H + +#include +#include +#include + +#include "sgx_urts.h" +#include "sgx_quote.h" + +// +// types +// + +#define SGXSD_AES_GCM_IV_SIZE 12 +#define SGXSD_AES_GCM_MAC_SIZE 16 +#define SGXSD_AES_GCM_KEY_SIZE 32 +#define SGXSD_CURVE25519_KEY_SIZE 32 +#define SGXSD_SHA256_HASH_SIZE 32 + +typedef struct sgxsd_aes_gcm_mac { + uint8_t data[SGXSD_AES_GCM_MAC_SIZE]; +} sgxsd_aes_gcm_mac_t; +_Static_assert(sizeof(sgxsd_aes_gcm_mac_t) == SGXSD_AES_GCM_MAC_SIZE, "Enclave ABI compatibility"); + +typedef struct sgxsd_aes_gcm_iv { + uint8_t data[SGXSD_AES_GCM_IV_SIZE]; +} sgxsd_aes_gcm_iv_t; +_Static_assert(sizeof(sgxsd_aes_gcm_iv_t) == SGXSD_AES_GCM_IV_SIZE, "Enclave ABI compatibility"); + +typedef struct sgxsd_aes_gcm_key { + uint8_t data[SGXSD_AES_GCM_KEY_SIZE]; +} sgxsd_aes_gcm_key_t; +_Static_assert(sizeof(sgxsd_aes_gcm_key_t) == SGXSD_AES_GCM_KEY_SIZE, "Enclave ABI compatibility"); + +typedef struct sgxsd_curve25519_public_key { + uint8_t x[SGXSD_CURVE25519_KEY_SIZE]; +} sgxsd_curve25519_public_key_t; +_Static_assert(sizeof(sgxsd_curve25519_public_key_t) == SGXSD_CURVE25519_KEY_SIZE, "Enclave ABI compatibility"); + +typedef struct sgxsd_request_negotiation_request { + sgxsd_curve25519_public_key_t client_pubkey; +} sgxsd_request_negotiation_request_t; +_Static_assert(sizeof(sgxsd_request_negotiation_request_t) == sizeof(sgxsd_curve25519_public_key_t), "Enclave ABI compatibility"); + +typedef struct sgxsd_pending_request_id { + uint8_t data[sizeof(uint64_t)]; + sgxsd_aes_gcm_iv_t iv; + sgxsd_aes_gcm_mac_t mac; +} sgxsd_pending_request_id_t; +_Static_assert(sizeof(sgxsd_pending_request_id_t) == sizeof(uint64_t) + sizeof(sgxsd_aes_gcm_iv_t) + sizeof(sgxsd_aes_gcm_mac_t), "Enclave ABI compatibility"); + +typedef struct sgxsd_request_negotiation_response { + sgxsd_curve25519_public_key_t server_static_pubkey; + sgxsd_curve25519_public_key_t server_ephemeral_pubkey; + struct { + uint8_t data[sizeof(sgxsd_pending_request_id_t)]; + sgxsd_aes_gcm_iv_t iv; + sgxsd_aes_gcm_mac_t mac; + } encrypted_pending_request_id; +} sgxsd_request_negotiation_response_t; +_Static_assert(sizeof(sgxsd_request_negotiation_response_t) == sizeof(sgxsd_curve25519_public_key_t) * 2 + sizeof(sgxsd_pending_request_id_t) + sizeof(sgxsd_aes_gcm_iv_t) + sizeof(sgxsd_aes_gcm_mac_t), "Enclave ABI compatibility"); + +typedef struct sgxsd_msg_tag { + union { + void *p_tag; + uint64_t tag; + }; +} sgxsd_msg_tag_t; +_Static_assert(sizeof(sgxsd_msg_tag_t) == sizeof(uint64_t), "Enclave ABI compatibility"); + +typedef struct sgxsd_msg_header { + sgxsd_aes_gcm_iv_t iv; + sgxsd_aes_gcm_mac_t mac; + sgxsd_pending_request_id_t pending_request_id; +} sgxsd_msg_header_t; +_Static_assert(sizeof(sgxsd_msg_header_t) == sizeof(sgxsd_aes_gcm_iv_t) + sizeof(sgxsd_aes_gcm_mac_t) + sizeof(sgxsd_pending_request_id_t), "Enclave ABI compatibility"); + +typedef struct sgxsd_node_init_args { + uint8_t pending_requests_table_order; +} sgxsd_node_init_args_t; + +typedef uint64_t sgxsd_server_state_handle_t; + +// +// public api (untrusted) +// + +#define sgxsd_status_ok() (sgxsd_status_t) { .ok = true, .name = "ok", .code = 0 } +#define sgxsd_status_error(Name) (sgxsd_status_t) { .ok = false, .name = Name, .code = 0 } +#define sgxsd_status_error_code(Name, SgxStatus) (sgxsd_status_t) { .ok = false, .name = Name, .code = SgxStatus } +typedef struct sgxsd_status { + bool ok; + const char *name; + int64_t code; +} sgxsd_status_t; + +typedef struct sgxsd_enclave { + sgx_enclave_id_t id; + union { + sgx_epid_group_id_t gid; + uint32_t gid32; + }; + sgx_launch_token_t launch_token; +} sgxsd_enclave_t; + +typedef sgxsd_status_t (*sgxsd_start_callback_t)(sgxsd_enclave_t, va_list); +sgxsd_status_t sgxsd_start(const char *enclave_path, bool debug, const sgx_launch_token_t *p_launch_token, const sgxsd_node_init_args_t *p_node_init_args, sgxsd_start_callback_t p_callback, ...); +sgxsd_status_t sgxsd_get_next_quote(sgx_enclave_id_t enclave_id, sgx_spid_t spid, const uint8_t *p_sig_rl, uint32_t sig_rl_size, sgx_quote_t *p_quote, uint32_t quote_size); + +// +// error codes +// + +typedef enum sgxsd_status_code { + SGXSD_ERROR_PENDING_REQUEST_NOT_FOUND = SGX_MK_ERROR(0x10001), +} sgxsd_status_code_t; + +#endif diff --git a/service/kbupd/res/enclave/f51f435802ada769e67aaf5744372bb7e7d519eecf996d335eb5b46b872b5789.so b/service/kbupd/res/enclave/f51f435802ada769e67aaf5744372bb7e7d519eecf996d335eb5b46b872b5789.so new file mode 100644 index 0000000..0d58948 Binary files /dev/null and b/service/kbupd/res/enclave/f51f435802ada769e67aaf5744372bb7e7d519eecf996d335eb5b46b872b5789.so differ diff --git a/service/kbupd/src/actor.rs b/service/kbupd/src/actor.rs new file mode 100644 index 0000000..6c426c3 --- /dev/null +++ b/service/kbupd/src/actor.rs @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use futures::future; +use futures::prelude::*; +use futures::sync::mpsc; +use futures::sync::oneshot; + +type Message = Box; + +pub struct Receiver(mpsc::UnboundedReceiver>); +pub struct Sender(mpsc::UnboundedSender>); + +// +// free functions +// + +pub fn channel() -> (Sender, Receiver) { + let (tx, rx) = mpsc::unbounded(); + (Sender(tx), Receiver(rx)) +} + +pub fn new(state: State) -> (Sender, impl Future) { + let (tx, rx) = self::channel(); + (tx, rx.enter_loop(state)) +} + +pub fn spawn(state: State, runtime: &Executor) -> Result, failure::Error> +where Executor: future::Executor + Send + 'static>>, + State: Send + 'static, +{ + let (tx, future) = self::new(state); + runtime.execute(Box::new(future)) + .map_err(|error: future::ExecuteError<_>| failure::format_err!("executor error: {:?}", error))?; + Ok(tx) +} + +// +// Receiver impls +// + +impl Receiver { + pub fn enter_loop(self, mut state: State) -> impl Future { + self.0.for_each(move |fun: Message| { + fun(&mut state); + Ok(()) + }) + } +} + +impl Stream for Receiver { + type Item = Message; + type Error = (); + + fn poll(&mut self) -> Poll, Self::Error> { + self.0.poll() + } +} + +// +// Sender impls +// + +impl Sender { + pub fn cast(&self, fun: impl FnOnce(&mut State) + Send + 'static) -> Result<(), ()> { + self.0.unbounded_send(Box::new(fun)).map_err(|_| ()) + } + + pub fn call(&self, fun: impl FnOnce(&mut State, oneshot::Sender>) + Send + 'static) + -> impl Future + Send + 'static + where T: Send + 'static, + E: From + Send + 'static + { + let (tx, rx) = oneshot::channel(); + let _ignore = self.0.unbounded_send(Box::new(move |manager: &mut State| { + fun(manager, tx) + })); + rx.from_err().and_then(|result: Result| result) + } + + pub fn sync_call(&self, fun: impl FnOnce(&mut State) -> Result + Send + 'static) + -> impl Future + Send + 'static + where T: Send + 'static, + E: From + Send + 'static + { + self.call(move |state: &mut State, reply_tx| { + let _ignore = reply_tx.send(fun(state)); + }) + } +} + +impl Clone for Sender { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} diff --git a/service/kbupd/src/api/auth/anonymous_user.rs b/service/kbupd/src/api/auth/anonymous_user.rs new file mode 100644 index 0000000..ee3714d --- /dev/null +++ b/service/kbupd/src/api/auth/anonymous_user.rs @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fmt; + +use super::*; + +pub struct AnonymousUser { + _private: (), +} + +#[derive(Clone, Copy, Default)] +pub struct AnonymousUserAuthenticator; + +pub enum AnonymousUserAuthenticationError { +} + +impl Authenticator for AnonymousUserAuthenticator { + type User = AnonymousUser; + type Error = AnonymousUserAuthenticationError; + fn authenticate(&self, _maybe_credentials: Option) -> Result { + Ok(AnonymousUser { + _private: (), + }) + } +} + +impl fmt::Display for AnonymousUserAuthenticationError { + fn fmt(&self, _fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self {} + } +} diff --git a/service/kbupd/src/api/auth/mod.rs b/service/kbupd/src/api/auth/mod.rs new file mode 100644 index 0000000..4ebd4a6 --- /dev/null +++ b/service/kbupd/src/api/auth/mod.rs @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod anonymous_user; +pub mod signal_user; + +use std::fmt; +use std::str; + +use hyper::header::{HeaderValue}; + +pub trait Authenticator { + type User: Send + 'static; + type Error: fmt::Display; + fn authenticate(&self, maybe_credentials: Option) -> Result; +} + +pub struct BasicCredentials { + username: String, + password: String, +} + +pub enum AuthorizationHeaderError { + UnsupportedAuthorizationMethod, + InvalidAuthorizationHeader, + InvalidCredentials, +} + +// +// BasicCredentials impls +// + +impl BasicCredentials { + pub fn try_from(header_value: &HeaderValue) -> Result { + let header = header_value.to_str().map_err(|_| AuthorizationHeaderError::InvalidAuthorizationHeader)?; + let mut header_parts = header.split(" "); + + if "Basic" != header_parts.next().ok_or(AuthorizationHeaderError::InvalidAuthorizationHeader)? { + return Err(AuthorizationHeaderError::UnsupportedAuthorizationMethod); + } + + let base64_value = header_parts.next().ok_or(AuthorizationHeaderError::InvalidAuthorizationHeader)?; + let concatenated_values_bytes = base64::decode(base64_value).map_err(|_| AuthorizationHeaderError::InvalidAuthorizationHeader)?; + let concatenated_values = str::from_utf8(&concatenated_values_bytes).map_err(|_| AuthorizationHeaderError::InvalidCredentials)?; + let mut credential_parts = concatenated_values.splitn(2, ":"); + + Ok(Self { + username: credential_parts.next().ok_or(AuthorizationHeaderError::InvalidCredentials)?.to_string(), + password: credential_parts.next().ok_or(AuthorizationHeaderError::InvalidCredentials)?.to_string(), + }) + } +} diff --git a/service/kbupd/src/api/auth/signal_user.rs b/service/kbupd/src/api/auth/signal_user.rs new file mode 100644 index 0000000..5a47499 --- /dev/null +++ b/service/kbupd/src/api/auth/signal_user.rs @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fmt; +use std::str; +use std::time::{Duration, SystemTime}; + +use ring::constant_time; +use ring::digest; +use ring::hmac; + +use super::*; +use crate::util; + +#[derive(Clone, Debug)] +pub struct SignalUser { + pub username: String, + _private: (), +} + +pub struct SignalUserAuthenticator { + hmac_key: hmac::SigningKey, +} + +#[derive(failure::Fail)] +pub enum SignalUserAuthenticationError { + #[fail(display = "unauthenticated")] + Unauthenticated, + #[fail(display = "invalid user authorization token")] + InvalidAuthorizationToken, + #[fail(display = "expired user authorization token")] + ExpiredAuthorizationToken, +} + +impl SignalUser { + #[cfg(test)] + pub fn new(username: String) -> Self { + Self { username, _private: () } + } +} + +// +// SignalUserAuthenticator impls +// + +impl Authenticator for SignalUserAuthenticator { + type User = SignalUser; + type Error = SignalUserAuthenticationError; + fn authenticate(&self, maybe_credentials: Option) -> Result { + let credentials = maybe_credentials.ok_or(SignalUserAuthenticationError::Unauthenticated)?; + let mut parts = credentials.password.split(":"); + let username = parts.next().ok_or(SignalUserAuthenticationError::InvalidAuthorizationToken)?; + let timestamp = parts.next().ok_or(SignalUserAuthenticationError::InvalidAuthorizationToken)?; + let signature = parts.next().ok_or(SignalUserAuthenticationError::InvalidAuthorizationToken)?; + if parts.next().is_some() { + return Err(SignalUserAuthenticationError::InvalidAuthorizationToken); + } + if username != credentials.username { + return Err(SignalUserAuthenticationError::InvalidAuthorizationToken); + } + if !self.is_valid_time(timestamp, SystemTime::now())? { + return Err(SignalUserAuthenticationError::ExpiredAuthorizationToken); + } + if !self.is_valid_signature(&format!("{}:{}", username, timestamp), signature)? { + return Err(SignalUserAuthenticationError::InvalidAuthorizationToken); + } + Ok(SignalUser { + username: credentials.username, + _private: (), + }) + } +} + +impl SignalUserAuthenticator { + pub fn new(shared_secret: &[u8]) -> Self { + Self { + hmac_key: hmac::SigningKey::new(&digest::SHA256, shared_secret), + } + } + fn is_valid_time(&self, timestamp: &str, now: SystemTime) -> Result { + let token_time: Duration = Duration::from_secs(timestamp.parse().map_err(|_| SignalUserAuthenticationError::InvalidAuthorizationToken)?); + let our_time: Duration = now.duration_since(SystemTime::UNIX_EPOCH).map_err(|_| SignalUserAuthenticationError::ExpiredAuthorizationToken)?; + let distance: Duration = our_time.checked_sub(token_time).unwrap_or_else(|| token_time - our_time); + Ok(distance.as_secs() < 86400) + } + fn is_valid_signature(&self, data: &str, signature: &str) -> Result { + let their_suffix: Vec = util::hex::parse(signature).map_err(|_| SignalUserAuthenticationError::InvalidAuthorizationToken)?; + let our_signature: hmac::Signature = hmac::sign(&self.hmac_key, data.as_bytes()); + let our_suffix: &[u8] = &our_signature.as_ref()[..10]; + Ok(constant_time::verify_slices_are_equal(our_suffix, &their_suffix).is_ok()) + } +} + +// +// SignalUserAuthenticationError impls +// + +impl fmt::Debug for SignalUserAuthenticationError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +#[cfg(test)] +pub mod test { + use std::fmt; + use std::time::{SystemTime}; + + use ring::digest; + use ring::hmac; + + use crate::util; + + pub struct MockSignalUserToken { + pub hmac_key: [u8; 32], + pub username: String, + } + impl MockSignalUserToken { + pub fn new(hmac_key: [u8; 32], username: String) -> Self { + Self { hmac_key, username } + } + } + impl fmt::Display for MockSignalUserToken { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); + let signdata = format!("{}:{}", &self.username, timestamp); + let signature = hmac::sign(&hmac::SigningKey::new(&digest::SHA256, &self.hmac_key), signdata.as_bytes()); + write!(fmt, "{}:{}", signdata, util::ToHex(&signature.as_ref()[..10])) + } + } +} diff --git a/service/kbupd/src/api/listener.rs b/service/kbupd/src/api/listener.rs new file mode 100644 index 0000000..19c1f06 --- /dev/null +++ b/service/kbupd/src/api/listener.rs @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::net::{ToSocketAddrs}; + +use futures::prelude::*; +use hyper; +use hyper::{Server}; +use hyper::server; +use hyper::server::conn::{AddrIncoming}; + +use crate::*; + +pub struct ApiListener { + service: Service, + hyper: server::Builder, +} + +impl ApiListener +where Service: hyper::service::Service, + Service: Clone + Send + 'static, + ::Future: Send, +{ + pub fn new(bind_address: impl ToSocketAddrs, service: Service) -> Result { + let hyper = + Server::try_bind(&util::to_socket_addr(bind_address)?)? + .http1_only(true); + Ok(Self { + service, + hyper, + }) + } + + pub fn into_future(self) -> impl Future { + let Self { service, hyper } = self; + let server = hyper.serve(move || { + let service: Result = Ok(service.clone()); + service + }); + + server.map_err(|error: hyper::Error| { + error!("hyper server error: {}", error); + }) + } +} diff --git a/service/kbupd/src/api/mod.rs b/service/kbupd/src/api/mod.rs new file mode 100644 index 0000000..439573f --- /dev/null +++ b/service/kbupd/src/api/mod.rs @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod auth; +pub mod listener; +pub mod service; + +use futures::prelude::*; +use kbupd_api::entities::*; + +use crate::*; + +#[cfg_attr(test, mockers_derive::mocked(BackupManagerMock))] +pub trait BackupManager { + type User; + fn get_token(&self, enclave_name: String, user: &Self::User) -> Box + Send>; + fn get_attestation(&self, enclave_name: String, user: &Self::User, request: RemoteAttestationRequest) -> Box + Send>; + fn put_backup_request(&self, enclave_name: String, user: &Self::User, request: KeyBackupRequest) -> Box + Send>; +} + +#[cfg(test)] +impl Clone for BackupManagerMock { + fn clone(&self) -> Self { + use mockers::Mock; + Self::new(self.mock_id, self.scenario.clone()) + } +} diff --git a/service/kbupd/src/api/service.rs b/service/kbupd/src/api/service.rs new file mode 100644 index 0000000..f5a690e --- /dev/null +++ b/service/kbupd/src/api/service.rs @@ -0,0 +1,976 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::ops::{Deref}; +use std::sync::{Arc}; + +use futures::future; +use futures::prelude::*; +use http::header; +use http::header::{HeaderValue}; +use http::request; +use hyper::{Body, Chunk, Method, Request, Response, StatusCode}; +use kbupd_api::entities::*; +use serde::{Deserialize, Serialize}; +use try_future::{TryFuture}; + +use super::*; +use super::auth::*; +use super::auth::anonymous_user::*; +use super::auth::signal_user::*; +use crate::*; +use crate::limits::rate_limiter::*; +use crate::metrics::*; + +#[derive(Clone)] +pub struct SignalApiService +where BackupManagerTy: Clone, +{ + router: route_recognizer::Router>>, + backup_manager: BackupManagerTy, + rate_limiters: SignalApiRateLimiters, + signal_user_authenticator: Arc, +} + +#[derive(Clone)] +pub struct SignalApiRateLimiters { + pub token: actor::Sender, + pub attestation: actor::Sender, + pub backup: actor::Sender, +} + +lazy_static::lazy_static! { + static ref AUTHENTICATION_FAILED_METER: Meter = METRICS.metric(&metric_name!("authentication", "failed")); + static ref AUTHENTICATION_SUCCEEDED_METER: Meter = METRICS.metric(&metric_name!("authentication", "succeeded")); + static ref HTTP_OK_METER: Meter = METRICS.metric(&metric_name!("http_ok")); + static ref HTTP_4XX_METER: Meter = METRICS.metric(&metric_name!("http_4xx")); + static ref HTTP_5XX_METER: Meter = METRICS.metric(&metric_name!("http_5xx")); + static ref HANDLER_ERROR_METER: Meter = METRICS.metric(&metric_name!("handler_error")); + static ref GET_TOKEN_TIMER: Timer = METRICS.metric(&metric_name!("get_token")); + static ref GET_ATTESTATION_TIMER: Timer = METRICS.metric(&metric_name!("get_attestation")); + static ref PUT_BACKUP_REQUEST_TIMER: Timer = METRICS.metric(&metric_name!("put_backup_request")); +} + +impl SignalApiService +where BackupManagerTy: BackupManager + Clone + Send + 'static, +{ + pub fn new(signal_user_authenticator: Arc, backup_manager: BackupManagerTy, rate_limiters: SignalApiRateLimiters) -> Self { + let mut router = route_recognizer::Router::new(); + + router.add("/v1/ping", Self::api_handler(move |_service, _params, request| { + match *request.method() { + Method::GET => Some(Self::get_request_handler(&AnonymousUserAuthenticator, |service, _params, user, request| { + service.ping(user, request) + })), + _ => None, + } + })); + router.add("/v1/token/:enclave_name", Self::api_handler(move |service, _params, request| { + match *request.method() { + Method::GET => Some(Self::get_request_handler(service.signal_user_authenticator.clone(), |service, params, user, request| { + service.get_token(¶ms["enclave_name"], user, request) + })), + _ => None, + } + })); + router.add("/v1/attestation/:enclave_name", Self::api_handler(move |service, _params, request| { + match *request.method() { + Method::PUT => Some(Self::request_handler(service.signal_user_authenticator.clone(), |service, params, _parts, user, request| { + service.get_attestation(¶ms["enclave_name"], user, request) + })), + _ => None, + } + })); + router.add("/v1/backup/:enclave_name", Self::api_handler(move |service, _params, request| { + match *request.method() { + Method::PUT => Some(Self::request_handler(service.signal_user_authenticator.clone(), |service, params, _parts, user, request| { + service.put_backup_request(¶ms["enclave_name"], user, request) + })), + _ => None, + } + })); + Self { + router, + backup_manager, + signal_user_authenticator, + rate_limiters, + } + } + + fn ping(&self, _user: AnonymousUser, _request: Request) + -> impl Future>, Error = failure::Error> { + Ok(Ok(PingResponse {})).into_future() + } + + fn get_token(&self, enclave_name: &str, user: SignalUser, _request: Request) + -> impl Future>, Error = failure::Error> { + let timer = GET_TOKEN_TIMER.time(); + let username = user.username.clone(); + let limit = self.rate_limiters.token.sync_call(move |rate_limiter: &mut RateLimiter| { + Self::handle_ratelimit_result(rate_limiter.validate(&username, 1)) + }); + let result = self.backup_manager.get_token(enclave_name.to_string(), &user); + let response = result.then(move |result: Result| { + timer.stop(); + match result { + Ok(response) => { + Ok(Ok(response)) + } + Err(EnclaveTransactionError::EnclaveNotFound) => { + let mut response = Response::default(); + *response.status_mut() = StatusCode::NOT_FOUND; + Ok(Err(response)) + } + Err(error) => { + Err(error.into()) + } + } + }); + limit.and_then(|maybe_ratelimit_response: Option>| { + match maybe_ratelimit_response { + Some(ratelimit_response) => TryFuture::from_ok(Err(ratelimit_response)), + None => response.into(), + } + }) + } + + fn get_attestation(&self, enclave_name: &str, user: SignalUser, request: RemoteAttestationRequest) + -> impl Future>, Error = failure::Error> { + let timer = GET_ATTESTATION_TIMER.time(); + let username = user.username.clone(); + let limit = self.rate_limiters.attestation.sync_call(move |rate_limiter: &mut RateLimiter| { + Self::handle_ratelimit_result(rate_limiter.validate(&username, 1)) + }); + let result = self.backup_manager.get_attestation(enclave_name.to_string(), &user, request); + let response = result.then(|result: Result| { + timer.stop(); + match result { + Ok(response) => { + Ok(Ok(response)) + } + Err(RemoteAttestationError::EnclaveNotFound) => { + let mut response = Response::default(); + *response.status_mut() = StatusCode::NOT_FOUND; + Ok(Err(response)) + } + Err(RemoteAttestationError::InvalidInput) => { + let mut response = Response::default(); + *response.status_mut() = StatusCode::BAD_REQUEST; + Ok(Err(response)) + } + Err(error) => { + Err(error.into()) + } + } + }); + limit.and_then(|maybe_ratelimit_response: Option>| { + match maybe_ratelimit_response { + Some(ratelimit_response) => TryFuture::from_ok(Err(ratelimit_response)), + None => response.into(), + } + }) + } + + fn put_backup_request(&self, enclave_name: &str, user: SignalUser, request: KeyBackupRequest) + -> impl Future>, Error = failure::Error> { + let timer = PUT_BACKUP_REQUEST_TIMER.time(); + let username = user.username.clone(); + let limit = self.rate_limiters.backup.sync_call(move |rate_limiter: &mut RateLimiter| { + Self::handle_ratelimit_result(rate_limiter.validate(&username, 1)) + }); + let result = self.backup_manager.put_backup_request(enclave_name.to_string(), &user, request); + let response = result.then(|result: Result| { + timer.stop(); + match result { + Ok(response) => { + Ok(Ok(response)) + } + Err(KeyBackupError::EnclaveNotFound) => { + let mut response = Response::default(); + *response.status_mut() = StatusCode::NOT_FOUND; + Ok(Err(response)) + } + Err(KeyBackupError::InvalidInput) => { + let mut response = Response::new(Body::from("InvalidInput")); + *response.status_mut() = StatusCode::BAD_REQUEST; + Ok(Err(response)) + } + Err(KeyBackupError::MacMismatch) => { + let mut response = Response::new(Body::from("MacMismatch")); + *response.status_mut() = StatusCode::BAD_REQUEST; + Ok(Err(response)) + } + Err(KeyBackupError::PendingRequestIdNotFound) => { + let mut response = Response::default(); + *response.status_mut() = StatusCode::GONE; + Ok(Err(response)) + } + Err(error) => { + Err(error.into()) + } + } + }); + limit.and_then(|maybe_ratelimit_response: Option>| { + match maybe_ratelimit_response { + Some(ratelimit_response) => TryFuture::from_ok(Err(ratelimit_response)), + None => response.into(), + } + }) + } + + fn api_handler(handler: F) -> Box> + where F: Fn(&Self, &route_recognizer::Params, &Request) -> Option + Send + Clone + 'static, + H: ApiHandler, + { + Box::new(SignalApiHandler::new(move |service: &Self, params: route_recognizer::Params, request: Request| { + match handler(service, ¶ms, &request) { + Some(request_handler) => { + future::Either::A(request_handler.handle(service, params, request)) + } + None => { + let mut response = Response::default(); + *response.status_mut() = StatusCode::METHOD_NOT_ALLOWED; + future::Either::B(Ok(response).into_future()) + } + } + })) + } + + fn get_request_handler(authenticator: impl Deref + Clone + Send + Sync + 'static, + handler: F) + -> impl ApiHandler + where ResTy: Serialize + 'static, + AuthTy: Authenticator, + F: Fn(&Self, &route_recognizer::Params, AuthTy::User, Request) + -> FRes + Send + Clone + 'static, + FRes: Future>, Error = failure::Error> + Send + 'static + { + SignalApiHandler::new(move |service: &Self, params: route_recognizer::Params, request: Request| { + let user = match Self::authorize_request(&*authenticator, &request) { + Err(error_response) => { + return future::Either::A(Ok(error_response).into_future()); + } + Ok(user) => user, + }; + + let handler_result = handler(service, ¶ms, user, request); + let response = handler_result.then(Self::handle_result); + future::Either::B(response) + }) + } + + fn request_handler(authenticator: impl Deref + Clone + Send + Sync + 'static, + handler: F) + -> impl ApiHandler + where ReqTy: for<'de> Deserialize<'de> + Send + 'static, + ResTy: Serialize + 'static, + AuthTy: Authenticator, + F: Fn(&Self, &route_recognizer::Params, request::Parts, AuthTy::User, ReqTy) + -> FRes + Send + Clone + 'static, + FRes: Future>, Error = failure::Error> + Send + 'static, + { + SignalApiHandler::new(move |service: &Self, params: route_recognizer::Params, request: Request| { + let user = match Self::authorize_request(&*authenticator, &request) { + Err(error_response) => { + return future::Either::A(Ok(error_response).into_future()); + } + Ok(user) => user, + }; + + let service = service.clone(); + let handler = handler.clone(); + let read_result = Self::read_request(request); + let response = read_result.and_then(move |read_result: Result<(request::Parts, ReqTy), Response>| { + let (request_parts, request) = match read_result { + Ok(ok_result) => ok_result, + Err(error_response) => return future::Either::A(Ok(error_response).into_future()), + }; + + let handler_result = handler(&service, ¶ms, request_parts, user, request); + let response = handler_result.then(Self::handle_result); + future::Either::B(response) + }); + future::Either::B(response) + }) + } + + fn authorize_request(authenticator: &AuthTy, request: &Request) -> Result> + where AuthTy: Authenticator, + { + let credentials = if let Some(header) = request.headers().get(hyper::header::AUTHORIZATION) { + match BasicCredentials::try_from(header) { + Err(_) => { + let mut response = Response::default(); + *response.status_mut() = StatusCode::BAD_REQUEST; + return Err(response); + } + Ok(credentials) => Some(credentials), + } + } else { + None + }; + match authenticator.authenticate(credentials) { + Err(_) => { + AUTHENTICATION_FAILED_METER.mark(); + let mut response = Response::default(); + *response.status_mut() = StatusCode::UNAUTHORIZED; + Err(response) + } + Ok(user) => { + AUTHENTICATION_SUCCEEDED_METER.mark(); + Ok(user) + } + } + } + + fn handle_ratelimit_result(result: Result<(), RateLimitError>) -> Result>, failure::Error> { + match result { + Ok(()) => { + Ok(None) + } + Err(RateLimitError::Exceeded(exceeded_error)) => { + let mut response = Response::new(Body::from(format!("{}", exceeded_error))); + *response.status_mut() = StatusCode::TOO_MANY_REQUESTS; + Ok(Some(response)) + } + Err(error @ RateLimitError::InternalError) => { + Err(error.into()) + } + } + } + + fn read_request(request: Request) + -> impl Future>, Error = failure::Error> + where ReqTy: for<'de> Deserialize<'de>, + { + let (request_parts, request_body) = request.into_parts(); + + let request_data = request_body.concat2().from_err(); + let response = request_data.and_then(|data: Chunk| { + match serde_json::from_slice(&data) { + Ok(deserialized) => { + future::Either::A(Ok(Ok((request_parts, deserialized))).into_future()) + } + Err(deserialize_error) => { + let mut response = Response::new(Body::from(deserialize_error.to_string())); + *response.status_mut() = StatusCode::BAD_REQUEST; + future::Either::B(Ok(Err(response)).into_future()) + } + } + }); + response + } + + fn handle_result(handler_result: Result>, failure::Error>) + -> Result, failure::Error> + where ResTy: Serialize + 'static, + { + match handler_result { + Ok(Ok(ok_response)) => { + let response_data = serde_json::to_vec(&ok_response)?; + let mut response = Response::builder(); + if let Some(headers) = response.headers_mut() { + headers.insert(header::CONTENT_TYPE, HeaderValue::from_static("application/json")); + } + Ok(response.body(Body::from(response_data))?) + } + Ok(Err(err_response)) => { + Ok(err_response) + } + Err(error) => { + error!("error during request processing: {}", error); + + let mut response = Response::default(); + *response.status_mut() = StatusCode::SERVICE_UNAVAILABLE; + Ok(response) + } + } + } +} + +impl hyper::service::Service for SignalApiService +where BackupManagerTy: Clone, +{ + type ReqBody = Body; + type ResBody = Body; + type Error = failure::Error; + type Future = Box, Error = Self::Error> + Send>; + fn call(&mut self, request: Request) -> Self::Future { + match self.router.recognize(request.uri().path()) { + Ok(matched) => { + let response = matched.handler.handle(self, matched.params, request); + + let logged_response = response.then(|result: Result, Self::Error>| { + match &result { + Ok(response) => { + if response.status().is_client_error() { + HTTP_4XX_METER.mark(); + } else if response.status().is_server_error() { + HTTP_5XX_METER.mark(); + } else { + HTTP_OK_METER.mark(); + } + } + Err(_error) => { + HANDLER_ERROR_METER.mark(); + } + } + result.into_future() + }); + Box::new(logged_response) + } + Err(_) => { + HTTP_4XX_METER.mark(); + let mut response = Response::default(); + *response.status_mut() = StatusCode::NOT_FOUND; + Box::new(Ok(response).into_future()) + } + } + } +} + +trait ApiHandler: Send { + type ApiService; + fn handle(&self, service: &Self::ApiService, params: route_recognizer::Params, request: Request) + -> Box, Error = failure::Error> + Send>; + fn clone_box(&self) -> Box>; +} + +impl Clone for Box> { + fn clone(&self) -> Self { + self.clone_box() + } +} + +struct SignalApiHandler(F, std::marker::PhantomData); + +impl SignalApiHandler { + pub fn new(handler: F) -> Self { + Self(handler, std::marker::PhantomData) + } +} + +impl ApiHandler for SignalApiHandler +where F: Fn(&ApiServiceTy, route_recognizer::Params, Request) -> FRes + Send + Clone + 'static, + FRes: Future, Error = failure::Error> + Send + 'static, + ApiServiceTy: Send + 'static, +{ + type ApiService = ApiServiceTy; + fn handle(&self, service: &ApiServiceTy, params: route_recognizer::Params, request: Request) + -> Box, Error = failure::Error> + Send> { + Box::new(self.0(service, params, request)) + } + fn clone_box(&self) -> Box> { + Box::new(SignalApiHandler(self.0.clone(), std::marker::PhantomData)) + } +} + +#[cfg(test)] +mod test { + use futures::future; + use futures::prelude::*; + use mockers::{Scenario}; + use mockers::matchers::*; + use tokio::runtime::current_thread; + + use super::*; + use super::super::{BackupManagerMock}; + use super::super::auth::signal_user::test::{MockSignalUserToken}; + use crate::limits::leaky_bucket::{LeakyBucketParameters}; + + struct SignalApiServiceTest { + scenario: Scenario, + runtime: current_thread::Runtime, + service: SignalApiService>>, + backup_manager: BackupManagerMockHandle, + valid_user: MockSignalUserToken, + } + + impl BackupManager for actor::Sender> { + type User = SignalUser; + fn get_token(&self, enclave_name: String, user: &Self::User) -> Box + Send> { + let user = user.clone(); + let call_result = self.sync_call(move |backup_manager: &mut BackupManagerMock| Ok(backup_manager.get_token(enclave_name, &user))); + Box::new(call_result.then(|result: Result<_, futures::Canceled>| result.unwrap())) + } + fn get_attestation(&self, enclave_name: String, user: &Self::User, request: RemoteAttestationRequest) -> Box + Send> { + let user = user.clone(); + let call_result = self.sync_call(move |backup_manager: &mut BackupManagerMock| Ok(backup_manager.get_attestation(enclave_name, &user, request))); + Box::new(call_result.then(|result: Result<_, futures::Canceled>| result.unwrap())) + } + fn put_backup_request(&self, enclave_name: String, user: &Self::User, request: KeyBackupRequest) -> Box + Send> { + let user = user.clone(); + let call_result = self.sync_call(move |backup_manager: &mut BackupManagerMock| Ok(backup_manager.put_backup_request(enclave_name, &user, request))); + Box::new(call_result.then(|result: Result<_, futures::Canceled>| result.unwrap())) + } + } + + impl SignalApiServiceTest { + pub fn new(ratelimiter_size: u64) -> Self { + let scenario = Scenario::new(); + let mut runtime = current_thread::Runtime::new().unwrap(); + + let runtime_handle = runtime.handle(); + + let (backup_manager_mock, backup_manager) = scenario.create_mock_for(); + let (backup_manager_tx, backup_manager_future) = actor::new(backup_manager_mock); + + let backup_manager_future: Box + 'static> = Box::new(backup_manager_future); + runtime.spawn(backup_manager_future); + + let hmac_secret = mocks::rand_array(); + let valid_user = MockSignalUserToken::new(hmac_secret, "valid_user".to_string()); + let authenticator = SignalUserAuthenticator::new(&hmac_secret); + let rate_limiters = SignalApiRateLimiters { + token: actor::spawn(RateLimiter::new("token", LeakyBucketParameters { size: ratelimiter_size, leak_rate: ratelimiter_size as f64 }), &runtime_handle).unwrap(), + attestation: actor::spawn(RateLimiter::new("attestation", LeakyBucketParameters { size: ratelimiter_size, leak_rate: ratelimiter_size as f64 }), &runtime_handle).unwrap(), + backup: actor::spawn(RateLimiter::new("backup", LeakyBucketParameters { size: ratelimiter_size, leak_rate: ratelimiter_size as f64 }), &runtime_handle).unwrap(), + }; + let service = SignalApiService::new(Arc::new(authenticator), backup_manager_tx, rate_limiters); + + Self { + scenario, + runtime, + service, + backup_manager, + valid_user, + } + } + pub fn serve(&self, incoming: mocks::AsyncPipeIncoming) -> impl Future { + let protocol = hyper::server::conn::Http::new(); + let hyper = hyper::server::Builder::new(incoming, protocol); + let hyper = hyper.http1_only(true); + let service = self.service.clone(); + let server = hyper.serve(move || { + let service: Result<_, failure::Error> = Ok(service.clone()); + service + }); + server.map_err(|error: hyper::Error| { + panic!("hyper server error: {}", error) + }) + } + + fn client(&mut self) -> hyper::Client { + let (connector, incoming) = mocks::AsyncPipeConnector::new(); + let client = hyper::client::Builder::default(); + + self.runtime.spawn(self.serve(incoming)); + client.build(connector) + } + } + + fn valid_remote_attestation_request() -> RemoteAttestationRequest { + RemoteAttestationRequest { + clientPublic: mocks::rand_array(), + } + } + + fn valid_key_backup_request() -> KeyBackupRequest { + KeyBackupRequest { + requestId: mocks::rand_bytes(vec![0; 50]), + iv: mocks::rand_array(), + data: mocks::rand_bytes(vec![0; 50]), + mac: mocks::rand_array(), + } + } + + #[test] + fn test_not_found() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::get("http://invalid/nonexistant") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::empty()) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::NOT_FOUND); + } + + #[test] + fn test_get_token_no_authorization() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::get("http://invalid/v1/token/test_enclave") + .body(Body::empty()) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::UNAUTHORIZED); + } + + #[test] + fn test_get_token_bad_authorization() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::get("http://invalid/v1/token/test_enclave") + .header(header::AUTHORIZATION, "zzzz") + .body(Body::empty()) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + } + + #[test] + fn test_get_token_unauthorized() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::get("http://invalid/v1/token/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, "invalid_password")) + .body(Body::empty()) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::UNAUTHORIZED); + } + + #[test] + fn test_get_token_ratelimit_exceeded() { + let mut test = SignalApiServiceTest::new(0); + + test.scenario.expect(test.backup_manager.get_token("test_enclave".to_string(), ANY) + .and_return(Box::new(future::lazy(|| -> Result<_,_> { panic!("response future was polled") })))); + + let request = Request::get("http://invalid/v1/token/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::empty()) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::TOO_MANY_REQUESTS); + } + + #[test] + fn test_get_token_valid() { + let mut test = SignalApiServiceTest::new(10000); + + let mock_response = GetTokenResponse { + backupId: mocks::rand_array::<[u8; 32]>().into(), + token: mocks::rand_array(), + tries: mocks::rand(), + }; + test.scenario.expect(test.backup_manager.get_token("test_enclave".to_string(), ANY) + .and_return(Box::new(Ok(mock_response.clone()).into_future()))); + + let request = Request::get("http://invalid/v1/token/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::empty()) + .unwrap(); + + let client = test.client(); + let response_data = test.runtime.block_on(client.request(request).and_then(|response: Response| { + response.into_body().concat2().from_err() + })).unwrap(); + let response: GetTokenResponse = serde_json::from_slice(&response_data).unwrap(); + assert_eq!(mock_response, response) + } + + #[test] + fn test_get_attestation_bad_method() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::get("http://invalid/v1/attestation/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::from(serde_json::to_string(&valid_remote_attestation_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::METHOD_NOT_ALLOWED); + } + + #[test] + fn test_get_attestation_no_authorization() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::put("http://invalid/v1/attestation/test_enclave") + .body(Body::from(serde_json::to_string(&valid_remote_attestation_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::UNAUTHORIZED); + } + + #[test] + fn test_get_attestation_bad_authorization() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::put("http://invalid/v1/attestation/test_enclave") + .header(header::AUTHORIZATION, "zzzz") + .body(Body::from(serde_json::to_string(&valid_remote_attestation_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + } + + #[test] + fn test_get_attestation_unauthorized() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::put("http://invalid/v1/attestation/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, "invalid_password")) + .body(Body::from(serde_json::to_string(&valid_remote_attestation_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::UNAUTHORIZED); + } + + #[test] + fn test_get_attestation_ratelimit_exceeded() { + let mut test = SignalApiServiceTest::new(0); + + test.scenario.expect(test.backup_manager.get_attestation("test_enclave".to_string(), ANY, ANY) + .and_return(Box::new(future::lazy(|| -> Result<_,_> { panic!("response future was polled") })))); + + let request = Request::put("http://invalid/v1/attestation/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::from(serde_json::to_string(&valid_remote_attestation_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::TOO_MANY_REQUESTS); + } + + #[test] + fn test_get_attestation_empty() { + let mut test = SignalApiServiceTest::new(10000); + + let request = Request::put("http://invalid/v1/attestation/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::empty()) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + } + + #[test] + fn test_get_attestation_invalid_input() { + let mut test = SignalApiServiceTest::new(10000); + + let mock_error = RemoteAttestationError::InvalidInput; + test.scenario.expect(test.backup_manager.get_attestation("test_enclave".to_string(), ANY, ANY) + .and_return(Box::new(Err(mock_error).into_future()))); + + let request = Request::put("http://invalid/v1/attestation/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::from(serde_json::to_string(&valid_remote_attestation_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + } + + #[test] + fn test_get_attestation_valid() { + let mut test = SignalApiServiceTest::new(10000); + + let mock_response = RemoteAttestationResponse { + serverEphemeralPublic: mocks::rand_array(), + serverStaticPublic: mocks::rand_array(), + quote: mocks::rand_bytes(vec![0; 100]), + iv: mocks::rand_array(), + ciphertext: mocks::rand_bytes(vec![0; 50]), + tag: mocks::rand_array(), + signature: mocks::rand_bytes(vec![0; 64]), + certificates: "test_certificates".to_string(), + signatureBody: "test_signature_body".to_string(), + }; + test.scenario.expect(test.backup_manager.get_attestation("test_enclave".to_string(), ANY, ANY) + .and_return(Box::new(Ok(mock_response.clone()).into_future()))); + + let request = Request::put("http://invalid/v1/attestation/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::from(serde_json::to_string(&valid_remote_attestation_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response_data = test.runtime.block_on(client.request(request).and_then(|response: Response| { + assert!(response.status().is_success()); + response.into_body().concat2().from_err() + })).unwrap(); + let response: RemoteAttestationResponse = serde_json::from_slice(&response_data).unwrap(); + assert_eq!(mock_response, response) + } + + #[test] + fn test_put_backup_request_bad_method() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::get("http://invalid/v1/backup/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::from(serde_json::to_string(&valid_key_backup_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::METHOD_NOT_ALLOWED); + } + + #[test] + fn test_put_backup_request_no_authorization() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::put("http://invalid/v1/backup/test_enclave") + .body(Body::from(serde_json::to_string(&valid_key_backup_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::UNAUTHORIZED); + } + + #[test] + fn test_put_backup_request_bad_authorization() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::put("http://invalid/v1/backup/test_enclave") + .header(header::AUTHORIZATION, "zzzz") + .body(Body::from(serde_json::to_string(&valid_key_backup_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + } + + #[test] + fn test_put_backup_request_unauthorized() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::put("http://invalid/v1/backup/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, "invalid_password")) + .body(Body::from(serde_json::to_string(&valid_key_backup_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::UNAUTHORIZED); + } + + #[test] + fn test_put_backup_request_ratelimit_exceeded() { + let mut test = SignalApiServiceTest::new(0); + + test.scenario.expect(test.backup_manager.put_backup_request("test_enclave".to_string(), ANY, ANY) + .and_return(Box::new(future::lazy(|| -> Result<_,_> { panic!("response future was polled") })))); + + let request = Request::put("http://invalid/v1/backup/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::from(serde_json::to_string(&valid_key_backup_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::TOO_MANY_REQUESTS); + } + + #[test] + fn test_put_backup_request_empty() { + let mut test = SignalApiServiceTest::new(10000); + let request = Request::put("http://invalid/v1/backup/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::empty()) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + } + + #[test] + fn test_put_backup_request_invalid_input() { + let mut test = SignalApiServiceTest::new(10000); + + let mock_error = KeyBackupError::InvalidInput; + test.scenario.expect(test.backup_manager.put_backup_request("test_enclave".to_string(), ANY, ANY) + .and_return(Box::new(Err(mock_error).into_future()))); + + let request = Request::put("http://invalid/v1/backup/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::from(serde_json::to_string(&valid_key_backup_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + } + + #[test] + fn test_put_backup_request_mac_mismatch() { + let mut test = SignalApiServiceTest::new(10000); + + let mock_error = KeyBackupError::MacMismatch; + test.scenario.expect(test.backup_manager.put_backup_request("test_enclave".to_string(), ANY, ANY) + .and_return(Box::new(Err(mock_error).into_future()))); + + let request = Request::put("http://invalid/v1/backup/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::from(serde_json::to_string(&valid_key_backup_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::BAD_REQUEST); + } + + #[test] + fn test_put_backup_request_pending_request_id_not_found() { + let mut test = SignalApiServiceTest::new(10000); + + let mock_error = KeyBackupError::PendingRequestIdNotFound; + test.scenario.expect(test.backup_manager.put_backup_request("test_enclave".to_string(), ANY, ANY) + .and_return(Box::new(Err(mock_error).into_future()))); + + let request = Request::put("http://invalid/v1/backup/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::from(serde_json::to_string(&valid_key_backup_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response = test.runtime.block_on(client.request(request)).unwrap(); + assert_eq!(response.status(), http::StatusCode::GONE); + } + + #[test] + fn test_put_backup_request_valid() { + let mut test = SignalApiServiceTest::new(10000); + + let mock_response = KeyBackupResponse { + iv: mocks::rand_array(), + data: mocks::rand_bytes(vec![0; 50]), + mac: mocks::rand_array(), + }; + test.scenario.expect(test.backup_manager.put_backup_request("test_enclave".to_string(), ANY, ANY) + .and_return(Box::new(Ok(mock_response.clone()).into_future()))); + + let request = Request::put("http://invalid/v1/backup/test_enclave") + .header(header::AUTHORIZATION, mocks::basic_auth(&test.valid_user.username, &test.valid_user)) + .body(Body::from(serde_json::to_string(&valid_key_backup_request()).unwrap())) + .unwrap(); + + let client = test.client(); + let response_data = test.runtime.block_on(client.request(request).and_then(|response: Response| { + assert!(response.status().is_success()); + response.into_body().concat2().from_err() + })).unwrap(); + let response: KeyBackupResponse = serde_json::from_slice(&response_data).unwrap(); + assert_eq!(mock_response, response) + } +} diff --git a/service/kbupd/src/backup/manager.rs b/service/kbupd/src/backup/manager.rs new file mode 100644 index 0000000..4be4e68 --- /dev/null +++ b/service/kbupd/src/backup/manager.rs @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::convert::{TryInto}; +use std::sync::{Arc}; + +use futures::future; +use futures::prelude::*; +use futures::sync::oneshot; +use kbupd_api::entities::*; +use kbupd_api::entities::{BackupId}; +use ring; + +use super::*; +use super::request_manager::*; +use crate::api::{BackupManager}; +use crate::api::auth::signal_user::{SignalUser}; +use crate::enclave::error::*; +use crate::protobufs::kbupd::*; + +pub struct SignalBackupManager { + enclave: BackupEnclaveTy, + backup_id_key: Arc, + request_manager: BackupRequestManagerSender, +} + +impl SignalBackupManager { + pub fn new(enclave: BackupEnclaveTy, + backup_id_key: ring::hmac::SigningKey, + request_manager: BackupRequestManagerSender) + -> Self { + Self { + enclave, + backup_id_key: Arc::new(backup_id_key), + request_manager, + } + } + + fn user_to_backup_id(&self, user: &SignalUser) -> BackupId { + let signature = ring::hmac::sign(&self.backup_id_key, user.username.as_bytes()); + signature.as_ref()[..32].try_into().unwrap_or_else(|_| unreachable!()) + } +} + +impl BackupManager for SignalBackupManager +where BackupEnclaveTy: BackupEnclave + Send + Clone + 'static, +{ + type User = SignalUser; + + fn get_token(&self, enclave_name: String, user: &SignalUser) -> Box + Send> { + let backup_id = self.user_to_backup_id(user); + let create_backup_reply = self.enclave.create_backup(enclave_name, backup_id); + let get_token_response = create_backup_reply.and_then(move |reply: CreateBackupReply| { + let tries: u32 = reply.tries.unwrap_or(0); + Ok(GetTokenResponse { + backupId: backup_id, + token: reply.token[..].try_into().unwrap_or_else(|_| unreachable!("token is always 32 bytes")), + tries: tries as u16, + }) + }); + Box::new(get_token_response) + } + + fn get_attestation(&self, enclave_name: String, _user: &SignalUser, request: RemoteAttestationRequest) + -> Box + Send> + { + self.enclave.get_attestation(enclave_name, request) + } + + fn put_backup_request(&self, enclave_name: String, user: &SignalUser, request: KeyBackupRequest) + -> Box + Send> + { + let backup_id = self.user_to_backup_id(user); + let request_id = request.requestId.clone(); + let (tx, rx) = oneshot::channel(); + + let maybe_cached_response = self.request_manager.call(move |request_manager: &mut BackupRequestManager, reply_tx| { + request_manager.start_request(backup_id, request_id, reply_tx) + }); + + let request_manager = self.request_manager.clone(); + let enclave = self.enclave.clone(); + let response_result = maybe_cached_response.and_then(move |maybe_cached_response: Option| { + if let Some(cached_response) = maybe_cached_response { + return future::Either::A(Ok(cached_response).into_future()); + } + + let request_id = request.requestId.clone(); + let response = enclave.put_backup_request(enclave_name, backup_id, request); + let response_result = response.then(move |response_result: Result| { + let cache_response_result = response_result.clone(); + let _ignore = request_manager.cast(move |request_manager: &mut BackupRequestManager| { + request_manager.finish_request(backup_id, request_id, cache_response_result) + }); + response_result + }); + future::Either::B(response_result) + }); + tokio::spawn(response_result.then(move |response_result: Result| { + let _ignore = tx.send(response_result); + Ok(()) + })); + + let response = rx.then(|rx_result: Result<_, futures::Canceled>| rx_result?); + Box::new(response) + } +} + +impl Clone for SignalBackupManager +where BackupEnclaveTy: BackupEnclave + Clone, +{ + fn clone(&self) -> Self { + Self { + enclave: self.enclave.clone(), + backup_id_key: self.backup_id_key.clone(), + request_manager: self.request_manager.clone(), + } + } +} diff --git a/service/kbupd/src/backup/mod.rs b/service/kbupd/src/backup/mod.rs new file mode 100644 index 0000000..f75c76a --- /dev/null +++ b/service/kbupd/src/backup/mod.rs @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod manager; +pub mod request_manager; + +use futures::prelude::*; +use kbupd_api::entities::*; +use kbupd_api::entities::{BackupId}; + +use crate::enclave::error::*; +use crate::protobufs::kbupd::*; + +pub trait BackupEnclave: Send { + fn create_backup(&self, enclave_name: String, backup_id: BackupId) -> Box + Send>; + fn get_attestation(&self, enclave_name: String, request: RemoteAttestationRequest) -> Box + Send>; + fn put_backup_request(&self, enclave_name: String, backup_id: BackupId, request: KeyBackupRequest) -> Box + Send>; +} diff --git a/service/kbupd/src/backup/request_manager.rs b/service/kbupd/src/backup/request_manager.rs new file mode 100644 index 0000000..84afbef --- /dev/null +++ b/service/kbupd/src/backup/request_manager.rs @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::collections::{hash_map, HashMap, VecDeque}; +use std::time::{Duration}; + +use futures::future; +use futures::prelude::*; +use futures::sync::oneshot; +use kbupd_api::entities::*; +use tokio::timer::delay_queue; +use tokio::timer::delay_queue::{DelayQueue}; + +use crate::*; + +const MAX_PENDING_REQUEST_WAITERS: usize = 5; + +pub type BackupRequestManagerSender = actor::Sender; + +pub struct BackupRequestManager { + requests: HashMap, + expirations: DelayQueue, + request_ttl: Duration, +} + +enum RequestState { + Pending(PendingRequestState), + Finished(FinishedRequestState), +} + +struct PendingRequestState { + request_id: Vec, + expiration: delay_queue::Key, + waiters: VecDeque, KeyBackupError>>>, +} + +struct FinishedRequestState { + request_id: Vec, + expiration: delay_queue::Key, + result: Box>, +} + +impl BackupRequestManager { + pub fn new(request_ttl: Duration) -> Self { + Self { + requests: Default::default(), + expirations: DelayQueue::new(), + request_ttl, + } + } + + pub fn enter_loop(self, mut rx: actor::Receiver) -> impl Future { + let mut maybe_state = Some(self); + future::poll_fn(move || { + let state = maybe_state.as_mut().unwrap_or_else(|| panic!("future already yielded")); + state.poll_expirations().map_err(|timer_error| { + error!("tokio timer error: {}", timer_error); + })?; + while let Some(fun) = futures::try_ready!(rx.poll()) { + fun(state); + } + Ok(Async::Ready(maybe_state.take().unwrap_or_else(|| unreachable!()))) + }) + } + + pub fn start_request(&mut self, + backup_id: BackupId, + request_id: Vec, + reply_tx: oneshot::Sender, KeyBackupError>>) + { + match self.requests.entry(backup_id) { + hash_map::Entry::Occupied(mut request_state_entry) => { + match request_state_entry.get_mut() { + RequestState::Pending(pending_request_state) + if pending_request_state.request_id == request_id => + { + pending_request_state.add_waiter(reply_tx); + } + RequestState::Finished(finished_request_state) + if finished_request_state.request_id == request_id => + { + let reply = finished_request_state.result.clone().map(Some); + let _ignore = reply_tx.send(reply); + } + request_state => { + let expiration = match request_state { + RequestState::Pending(pending_request_state) => pending_request_state.expiration.clone(), + RequestState::Finished(finished_request_state) => finished_request_state.expiration.clone(), + }; + self.expirations.reset(&expiration, self.request_ttl); + + let pending_request_state = PendingRequestState::new(request_id, expiration); + *request_state = RequestState::Pending(pending_request_state); + let _ignore = reply_tx.send(Ok(None)); + } + } + } + hash_map::Entry::Vacant(request_state_entry) => { + let expiration = self.expirations.insert(backup_id, self.request_ttl); + let pending_request_state = PendingRequestState::new(request_id, expiration); + request_state_entry.insert(RequestState::Pending(pending_request_state)); + let _ignore = reply_tx.send(Ok(None)); + } + } + } + + pub fn finish_request(&mut self, + backup_id: BackupId, + request_id: Vec, + result: Result) + { + let request_state = match self.requests.entry(backup_id) { + hash_map::Entry::Occupied(request_state_entry) => request_state_entry.into_mut(), + hash_map::Entry::Vacant(_request_state_entry) => return, + }; + + let pending_request_state = match request_state { + RequestState::Pending(pending_request_state) + if pending_request_state.request_id == request_id => pending_request_state, + _ => return, + }; + + for reply_tx in pending_request_state.waiters.drain(..) { + let _ignore = reply_tx.send(result.clone().map(Some)); + } + + *request_state = RequestState::Finished(FinishedRequestState { + request_id, + expiration: pending_request_state.expiration.clone(), + result: Box::new(result), + }); + } + + fn poll_expirations(&mut self) -> Poll<(), tokio::timer::Error> { + while let Some(expiration) = futures::try_ready!(self.expirations.poll()) { + self.requests.remove(expiration.get_ref()); + } + Ok(Async::Ready(())) + } +} + +impl PendingRequestState { + fn new(request_id: Vec, expiration: delay_queue::Key) -> Self + { + Self { + request_id, + expiration, + waiters: Default::default(), + } + } + fn add_waiter(&mut self, reply_tx: oneshot::Sender, KeyBackupError>>) { + while self.waiters.len() > MAX_PENDING_REQUEST_WAITERS { + drop(self.waiters.pop_front()); + } + self.waiters.push_back(reply_tx); + } +} + +#[cfg(test)] +mod test { + use std::time::{Duration, Instant}; + + use futures::sync::oneshot; + use kbupd_api::entities::*; + use tokio::timer::{Delay}; + use tokio::runtime::current_thread::{Runtime}; + + use super::*; + use crate::mocks::{rand_array, rand_bytes}; + + fn backup_id() -> BackupId { + rand_array::<[u8; 32]>().into() + } + + fn request_id() -> [u8; 32] { + rand_array() + } + + fn response() -> KeyBackupResponse { + KeyBackupResponse { + iv: rand_array(), + data: rand_bytes(vec![0; 32]), + mac: rand_array(), + } + } + + fn runtime() -> Runtime { + Runtime::new().unwrap() + } + + fn sleep(runtime: &mut Runtime, duration: Duration) { + runtime.block_on(Delay::new(Instant::now() + Duration::from_millis(1) + duration)).unwrap() + } + + struct TestBackupRequestManager { + state: BackupRequestManager, + tx: actor::Sender, + rx: actor::Receiver, + } + + impl TestBackupRequestManager { + fn new(state: BackupRequestManager) -> Self { + let (tx, rx) = actor::channel(); + Self { state, tx, rx } + } + fn sender(&self) -> &actor::Sender { + &self.tx + } + fn flush(self, runtime: &mut Runtime) -> Self { + drop(self.tx); + let state = runtime.block_on(self.state.enter_loop(self.rx)).unwrap(); + let (tx, rx) = actor::channel(); + Self { state, tx, rx } + } + } + + impl Default for TestBackupRequestManager { + fn default() -> Self { + Self::new(BackupRequestManager::new(Duration::from_secs(86400))) + } + } + + #[test] + fn test_expire_request() { + let mut runtime = runtime(); + let request_ttl = Duration::from_millis(0); + let request_manager = TestBackupRequestManager::new(BackupRequestManager::new(request_ttl)); + let (backup_id, request_id) = (backup_id(), request_id()); + + let (reply_1_tx, mut reply_1_rx) = oneshot::channel(); + let (reply_2_tx, mut reply_2_rx) = oneshot::channel(); + let (reply_3_tx, mut reply_3_rx) = oneshot::channel(); + let (reply_4_tx, mut reply_4_rx) = oneshot::channel(); + let (reply_5_tx, mut reply_5_rx) = oneshot::channel(); + + request_manager.sender().cast(move |request_manager: &mut BackupRequestManager| { + request_manager.start_request(backup_id, request_id.to_vec(), reply_1_tx); + request_manager.start_request(backup_id, request_id.to_vec(), reply_2_tx); + }).unwrap(); + + let request_manager = request_manager.flush(&mut runtime); + sleep(&mut runtime, request_ttl); + + request_manager.sender().cast(move |request_manager: &mut BackupRequestManager| { + request_manager.start_request(backup_id, request_id.to_vec(), reply_3_tx); + }).unwrap(); + + let request_manager = request_manager.flush(&mut runtime); + sleep(&mut runtime, request_ttl); + + request_manager.sender().cast(move |request_manager: &mut BackupRequestManager| { + request_manager.finish_request(backup_id, request_id.to_vec(), Ok(response())); + request_manager.start_request( backup_id, request_id.to_vec(), reply_4_tx); + request_manager.start_request( backup_id, request_id.to_vec(), reply_5_tx); + request_manager.finish_request(backup_id, request_id.to_vec(), Ok(response())); + }).unwrap(); + + let request_manager = request_manager.flush(&mut runtime); + drop(request_manager); + + let reply_1 = reply_1_rx.try_recv().unwrap().unwrap(); + assert!(reply_1.unwrap().is_none()); + + assert!(reply_2_rx.try_recv().is_err()); + + let reply_3 = reply_3_rx.try_recv().unwrap().unwrap(); + assert!(reply_3.unwrap().is_none()); + + let reply_4 = reply_4_rx.try_recv().unwrap().unwrap(); + assert!(reply_4.unwrap().is_none()); + + let reply_5 = reply_5_rx.try_recv().unwrap().unwrap(); + assert!(reply_5.unwrap().is_some()); + } + + #[test] + fn test_replace_request() { + let mut request_manager = BackupRequestManager::new(Duration::from_secs(86400)); + let backup_id = backup_id(); + let request_id_1 = request_id(); + let request_id_2 = request_id(); + + let (reply_1_tx, mut reply_1_rx) = oneshot::channel(); + let (reply_2_tx, mut reply_2_rx) = oneshot::channel(); + let (reply_3_tx, mut reply_3_rx) = oneshot::channel(); + + request_manager.start_request(backup_id, request_id_1.to_vec(), reply_1_tx); + request_manager.start_request(backup_id, request_id_1.to_vec(), reply_2_tx); + request_manager.start_request(backup_id, request_id_2.to_vec(), reply_3_tx); + + let reply_1 = reply_1_rx.try_recv().unwrap().unwrap(); + assert!(reply_1.unwrap().is_none()); + + assert!(reply_2_rx.try_recv().is_err()); + + let reply_3 = reply_3_rx.try_recv().unwrap().unwrap(); + assert!(reply_3.unwrap().is_none()); + } + + #[test] + fn test_finish_request_ok() { + let mut request_manager = BackupRequestManager::new(Duration::from_secs(86400)); + let backup_id = backup_id(); + let request_id = request_id(); + + let (reply_1_tx, mut reply_1_rx) = oneshot::channel(); + let (reply_2_tx, mut reply_2_rx) = oneshot::channel(); + let (reply_3_tx, mut reply_3_rx) = oneshot::channel(); + + request_manager.start_request( backup_id, request_id.to_vec(), reply_1_tx); + request_manager.start_request( backup_id, request_id.to_vec(), reply_2_tx); + request_manager.finish_request(backup_id, request_id.to_vec(), Ok(response())); + request_manager.start_request( backup_id, request_id.to_vec(), reply_3_tx); + + let reply_1 = reply_1_rx.try_recv().unwrap().unwrap(); + assert!(reply_1.unwrap().is_none()); + + let reply_2 = reply_2_rx.try_recv().unwrap().unwrap(); + assert!(reply_2.unwrap().is_some()); + + let reply_3 = reply_3_rx.try_recv().unwrap().unwrap(); + assert!(reply_3.unwrap().is_some()); + } + + #[test] + fn test_finish_request_err() { + let mut request_manager = BackupRequestManager::new(Duration::from_secs(86400)); + let backup_id = backup_id(); + let request_id = request_id(); + + let (reply_1_tx, mut reply_1_rx) = oneshot::channel(); + let (reply_2_tx, mut reply_2_rx) = oneshot::channel(); + let (reply_3_tx, mut reply_3_rx) = oneshot::channel(); + + request_manager.start_request( backup_id, request_id.to_vec(), reply_1_tx); + request_manager.start_request( backup_id, request_id.to_vec(), reply_2_tx); + request_manager.finish_request(backup_id, request_id.to_vec(), Err(KeyBackupError::RequestCanceled)); + request_manager.start_request( backup_id, request_id.to_vec(), reply_3_tx); + + let reply_1 = reply_1_rx.try_recv().unwrap().unwrap(); + assert!(reply_1.unwrap().is_none()); + + let reply_2 = reply_2_rx.try_recv().unwrap().unwrap(); + assert!(reply_2.is_err()); + + let reply_3 = reply_3_rx.try_recv().unwrap().unwrap(); + assert!(reply_3.is_err()); + } + + #[test] + fn test_finish_replaced_request() { + let mut request_manager = BackupRequestManager::new(Duration::from_secs(86400)); + let backup_id = backup_id(); + let request_id_1 = request_id(); + let request_id_2 = request_id(); + + let (reply_1_tx, mut reply_1_rx) = oneshot::channel(); + let (reply_2_tx, mut reply_2_rx) = oneshot::channel(); + let (reply_3_tx, mut reply_3_rx) = oneshot::channel(); + let (reply_4_tx, mut reply_4_rx) = oneshot::channel(); + + request_manager.start_request( backup_id, request_id_1.to_vec(), reply_1_tx); + request_manager.start_request( backup_id, request_id_1.to_vec(), reply_2_tx); + request_manager.start_request( backup_id, request_id_2.to_vec(), reply_3_tx); + request_manager.start_request( backup_id, request_id_2.to_vec(), reply_4_tx); + request_manager.finish_request(backup_id, request_id_1.to_vec(), Err(KeyBackupError::RequestCanceled)); + request_manager.finish_request(backup_id, request_id_2.to_vec(), Ok(response())); + + let reply_1 = reply_1_rx.try_recv().unwrap().unwrap(); + assert!(reply_1.unwrap().is_none()); + + assert!(reply_2_rx.try_recv().err().is_some()); + + let reply_3_rx = reply_3_rx.try_recv().unwrap().unwrap(); + assert!(reply_3_rx.unwrap().is_none()); + + let reply_4_rx = reply_4_rx.try_recv().unwrap().unwrap(); + assert!(reply_4_rx.unwrap().is_some()); + } + + #[test] + fn test_replace_finished_request() { + let mut request_manager = BackupRequestManager::new(Duration::from_secs(86400)); + let backup_id = backup_id(); + let request_id_1 = request_id(); + let request_id_2 = request_id(); + + let (reply_1_tx, mut reply_1_rx) = oneshot::channel(); + let (reply_2_tx, mut reply_2_rx) = oneshot::channel(); + let (reply_3_tx, mut reply_3_rx) = oneshot::channel(); + + request_manager.start_request( backup_id, request_id_1.to_vec(), reply_1_tx); + request_manager.finish_request(backup_id, request_id_1.to_vec(), Err(KeyBackupError::RequestCanceled)); + request_manager.start_request( backup_id, request_id_2.to_vec(), reply_2_tx); + request_manager.start_request( backup_id, request_id_2.to_vec(), reply_3_tx); + request_manager.finish_request(backup_id, request_id_2.to_vec(), Ok(response())); + + let reply_1 = reply_1_rx.try_recv().unwrap().unwrap(); + assert!(reply_1.unwrap().is_none()); + + let reply_2_rx = reply_2_rx.try_recv().unwrap().unwrap(); + assert!(reply_2_rx.unwrap().is_none()); + + let reply_3_rx = reply_3_rx.try_recv().unwrap().unwrap(); + assert!(reply_3_rx.unwrap().is_some()); + } +} diff --git a/service/kbupd/src/bin/kbupctl.rs b/service/kbupd/src/bin/kbupctl.rs new file mode 100644 index 0000000..5da27e9 --- /dev/null +++ b/service/kbupd/src/bin/kbupctl.rs @@ -0,0 +1,923 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::convert::{TryInto}; +use std::collections::*; +use std::io; +use std::str::{FromStr}; + +use failure::{ResultExt}; +use futures::future; +use futures::prelude::*; +use futures::stream::{SplitSink}; +use futures::sync::mpsc; +use futures::sync::oneshot; +use kbupd::*; +use kbupd::protobufs::kbupd::*; +use kbupd_util::{hex, ToHex}; +use log::*; +use rand::{RngCore}; +use rand::rngs::{OsRng}; +use tokio::net::{TcpStream}; +use tokio_codec::{Decoder}; + +type ControlCodec = kbupd::ControlCodec; +type ControlFramed = tokio_codec::Framed; + +fn main() -> Result<(), failure::Error> { + let arguments = parse_arguments(); + + let (subcommand_name, subcommand_arguments) = arguments.subcommand(); + let subcommand_arguments = subcommand_arguments.unwrap(); + + let connect_address = arguments.value_of("connect_address").unwrap_or_default(); + let debug = arguments.is_present("debug"); + let enclave_name = subcommand_arguments.value_of("enclave_name").map(str::to_string); + + let log_level = if debug { + log::Level::Debug + } else { + log::Level::Info + }; + let (logger, logger_guard) = logger::Logger::new_with_guard(log_level); + log::set_boxed_logger(Box::new(logger))?; + log::set_max_level(log_level.to_level_filter()); + + let connect_future = TcpStream::connect(&kbupd_util::to_socket_addr(connect_address)?) + .map(|stream: TcpStream| { + let _ignore = stream.set_nodelay(true); + ControlCodec::new().framed(stream) + }) + .map_err(|error: io::Error| { + error!("error connecting: {}", error); + }); + + match subcommand_name { + "info" | "status" => { + let print_fun = match subcommand_name { + "info" => print_info, + "status" => print_status, + _ => unreachable!(), + }; + let control_request = ControlRequest { + id: Default::default(), + data: Some(control_request::Data::GetStatusControlRequest(GetStatusControlRequest { + memory_status: subcommand_arguments.is_present("detailed_memory_status"), + })), + }; + let connection = connect_future + .and_then(move |framed: ControlFramed| { + daemon_call(framed, control_request) + .map_err(|error: io::Error| { + error!("error sending command: {}", error); + }) + }) + .map(move |(reply, _framed): (ControlReply, ControlFramed)| { + if let Some(control_reply::Data::GetStatusControlReply(reply)) = reply.data { + print_fun(enclave_name, reply); + } else { + error!("error fetching status: {:?}", reply.data); + } + }); + tokio::run(connection); + } + "metrics" => { + let control_request = ControlRequest { + id: Default::default(), + data: Some(control_request::Data::GetMetricsControlRequest(GetMetricsControlRequest { + })), + }; + let connection = connect_future + .and_then(move |framed: ControlFramed| { + daemon_call(framed, control_request) + .map_err(|error: io::Error| { + error!("error sending command: {}", error); + }) + }) + .map(move |(reply, _framed): (ControlReply, ControlFramed)| { + if let Some(control_reply::Data::GetMetricsControlReply(reply)) = reply.data { + println!("{}", &reply.metrics_json); + } else { + error!("error fetching status: {:?}", reply.data); + } + }); + tokio::run(connection); + } + "xfer" => { + let (xfer_subcommand_name, xfer_subcommand_arguments) = subcommand_arguments.subcommand(); + let _xfer_subcommand_arguments = xfer_subcommand_arguments.unwrap(); + let xfer_control_command = match xfer_subcommand_name { + "start" => XferControlCommand::Start, + "pause" => XferControlCommand::Pause, + "resume" => XferControlCommand::Resume, + "finish" => XferControlCommand::Finish, + _ => panic!("No subcommand"), + }; + + let control_request = ControlRequest { + id: Default::default(), + data: Some(control_request::Data::XferControlRequest(XferControlRequest { + enclave_name, + xfer_control_command: xfer_control_command as i32, + })), + }; + + let connection = connect_future + .and_then(move |framed: ControlFramed| { + daemon_call(framed, control_request) + .map_err(|error: io::Error| { + error!("error sending command: {}", error); + }) + }) + .map(move |(reply, _framed): (ControlReply, ControlFramed)| { + let result = match reply.data { + Some(control_reply::Data::XferControlReply(reply)) => { + match UntrustedXferReplyStatus::from_i32(reply.status) { + Some(UntrustedXferReplyStatus::Ok) => Ok(()), + Some(UntrustedXferReplyStatus::NotLeader) => Err("not_leader"), + Some(UntrustedXferReplyStatus::InvalidState) => Err("invalid_state"), + Some(UntrustedXferReplyStatus::Unknown) | + None => Err("unknown"), + } + } + _ => Err("unknown"), + }; + match result { + Ok(()) => println!("ok"), + Err(status) => { + println!("{}", status); + error!("error fetching info: {}", status); + } + } + }); + tokio::run(connection); + } + "reconnect-peer" => { + let peer_node_id = hex::parse(subcommand_arguments.value_of("peer_node_id").unwrap())?; + let peer_address = subcommand_arguments.value_of("peer_address"); + let control_request = ControlRequest { + id: Default::default(), + data: Some(control_request::Data::ForcePeerReconnectRequest(ForcePeerReconnectRequest { + enclave_name, + node_id: peer_node_id, + address: peer_address.map(str::to_string), + })), + }; + let connection = connect_future + .and_then(move |framed: ControlFramed| { + daemon_call(framed, control_request) + .map_err(|error: io::Error| { + error!("error sending command: {}", error); + }) + }) + .map(|(reply, _framed): (ControlReply, ControlFramed)| { + if reply.data.is_some() { + error!("error forcing reconnect: {:?}", reply.data); + } + }); + tokio::run(connection); + } + "disconnect-peer" => { + let peer_node_id = hex::parse(subcommand_arguments.value_of("peer_node_id").unwrap())?; + let control_request = ControlRequest { + id: Default::default(), + data: Some(control_request::Data::PeerDisconnectRequest(PeerDisconnectRequest { + enclave_name, + node_id: peer_node_id, + })), + }; + let connection = connect_future + .and_then(move |framed: ControlFramed| { + daemon_call(framed, control_request) + .map_err(|error: io::Error| { + error!("error sending command: {}", error); + }) + }) + .map(|(reply, _framed): (ControlReply, ControlFramed)| { + if reply.data.is_some() { + error!("error forcing disconnect: {:?}", reply.data); + } + }); + tokio::run(connection); + } + "client" => { + let (client_subcommand_name, client_subcommand_arguments) = subcommand_arguments.subcommand(); + let client_subcommand_arguments = client_subcommand_arguments.unwrap(); + + let request = ClientRequestParameters { + enclave_name: enclave_name.unwrap_or_default(), + subcommand: client_subcommand_name.to_string(), + service_id: parse_argument(client_subcommand_arguments.value_of("service_id"), hex::parse)?, + maybe_token: parse_argument(client_subcommand_arguments.value_of("request_token"), hex::parse)?, + backup_id: parse_argument(client_subcommand_arguments.value_of("backup_id"), hex::parse)?, + backup_data: parse_argument(client_subcommand_arguments.value_of("backup_data"), hex::parse)?, + backup_pin: parse_argument(client_subcommand_arguments.value_of("backup_pin"), hex::parse)?, + backup_tries: parse_argument(client_subcommand_arguments.value_of("backup_tries"), u32::from_str)?, + valid_from: parse_argument(client_subcommand_arguments.value_of("request_valid_from"), u64::from_str)?.unwrap_or(0), + }; + + let count = u64::from_str(client_subcommand_arguments.value_of("request_count").unwrap_or_default())?; + let max_parallel = parse_argument(client_subcommand_arguments.value_of("max_parallel"), u64::from_str)?.unwrap_or(count); + + let connection = connect_future + .and_then(move |framed: ControlFramed| { + let (request_tx, request_rx) = mpsc::unbounded(); + let request_tx = ControlClientSender { request_tx }; + tokio::spawn(control_client(framed, request_rx)); + let future_count = count.min(max_parallel); + let futures = (0..future_count).map(|request_id: u64| { + let request_tx = request_tx.clone(); + let request = request.clone(); + futures::future::loop_fn(request_id, move |request_id: u64| { + info!("request {}", request_id); + let future: Box + Send + 'static> = match request.subcommand.as_str() { + "create" => { + let create_backup_request = transaction_control_request::Data::CreateBackupRequest(CreateBackupRequest { + backup_id: BackupId { id: unwrap_or_random_bytes(32, request.backup_id.clone()) }, + }); + let future = send_transaction_request(request_tx.clone(), request.enclave_name.clone(), request_id, create_backup_request) + .map(|_reply| ()); + Box::new(future) + } + "delete" => { + let delete_backup_request = transaction_control_request::Data::DeleteBackupRequest(DeleteBackupRequest { + backup_id: BackupId { id: unwrap_or_random_bytes(32, request.backup_id.clone()) }, + }); + let future = send_transaction_request(request_tx.clone(), request.enclave_name.clone(), request_id, delete_backup_request) + .map(|_reply| ()); + Box::new(future) + } + _ => { + let future = client_request(request_tx.clone(), request.clone(), request_id); + Box::new(future) + } + }; + future.map(move |()| { + let request_id = request_id + max_parallel; + if request_id < count { + futures::future::Loop::Continue(request_id) + } else { + futures::future::Loop::Break(()) + } + }) + }) + }); + futures::stream::futures_unordered(futures).for_each(|_| Ok(())) + }); + let mut runtime = tokio::runtime::Runtime::new()?; + let _ignore = runtime.block_on(connection); + } + _ => { + } + } + drop(logger_guard); + Ok(()) +} + +fn parse_argument(maybe_argument: Option, parse_fun: F) -> Result, E> +where F: Fn(T) -> Result +{ + if let Some(argument) = maybe_argument { + Ok(Some(parse_fun(argument)?)) + } else { + Ok(None) + } +} + +fn unwrap_or_random_bytes(size: usize, maybe_bytes: Option>) -> Vec { + maybe_bytes.unwrap_or_else(|| { + let mut bytes = vec![0; size]; + rand::thread_rng().fill_bytes(&mut bytes); + bytes + }) +} + +fn print_info(maybe_enclave_name: Option, status: GetStatusControlReply) { + let maybe_enclave_status = if let Some(enclave_name) = &maybe_enclave_name { + status.enclaves.into_iter().find(|enclave_status: &EnclaveStatus| &enclave_status.name == enclave_name) + } else { + status.enclaves.into_iter().next() + }; + let enclave_status = match maybe_enclave_status { + Some(enclave_status) => enclave_status, + None => { + if let Some(enclave_name) = &maybe_enclave_name { + error!("enclave status reply did not contain requested enclave {}", enclave_name); + } else { + error!("enclave status reply did not contain an enclave"); + } + return; + } + }; + + println!("node_id={}", ToHex(&enclave_status.node_id)); + + match enclave_status.status { + Some(enclave_status::Status::ReplicaStatus(replica_status)) => { + if let Some(partition_status) = &replica_status.partition { + if let Some(service_id) = &partition_status.service_id { + println!("service_id={}", ToHex(service_id)); + } + println!("group_id={}", ToHex(&partition_status.group_id)); + } + } + Some(enclave_status::Status::FrontendStatus(_frontend_status)) => { + } + None => (), + } +} + +fn print_status(maybe_enclave_name: Option, status: GetStatusControlReply) { + let enclave_statuses = status.enclaves.into_iter().filter(|enclave_status: &EnclaveStatus| { + if let Some(enclave_name) = &maybe_enclave_name { + &enclave_status.name == enclave_name + } else { + true + } + }); + + for enclave_status in enclave_statuses { + println!("{:#}", enclave_status); + } +} + +#[derive(Clone)] +struct ClientRequestParameters { + enclave_name: String, + subcommand: String, + service_id: Option>, + maybe_token: Option>, + backup_id: Option>, + backup_data: Option>, + backup_pin: Option>, + backup_tries: Option, + valid_from: u64, +} + +fn client_request(request_tx: ControlClientSender, + request: ClientRequestParameters, + request_id: u64) + -> impl Future { + let ClientRequestParameters { + enclave_name, + subcommand, + service_id, + maybe_token, + backup_id, + backup_data, + backup_pin, + backup_tries, + valid_from, + } = request; + + let mut random = OsRng::new().expect("rand error"); + let client = kbupd_client::Client::new(&mut random); + let client_pubkey = client.client_pubkey().to_vec(); + let backup_id = unwrap_or_random_bytes(32, backup_id); + + let enclave_name_1 = enclave_name.clone(); + let request_tx_1 = request_tx.clone(); + let backup_id_1 = backup_id.clone(); + + let create_backup_future = if let Some(token) = maybe_token { + future::Either::A(Ok(token).into_future()) + } else { + let create_backup_request = transaction_control_request::Data::CreateBackupRequest(CreateBackupRequest { + backup_id: BackupId { id: backup_id.clone() }, + }); + let reply = send_transaction_request(request_tx.clone(), enclave_name.clone(), request_id, create_backup_request); + let token = reply.and_then(|reply: TransactionControlReply| { + if let Some(transaction_control_reply::Data::CreateBackupReply(create_backup_reply)) = reply.data { + Ok(create_backup_reply.token) + } else { + error!("received wrong transaction reply: {:?}", reply); + Err(()) + } + }); + future::Either::B(token) + }; + create_backup_future + .map(move |token: Vec| { + let request = match subcommand.as_str() { + "backup" => { + kbupd_client::Request { + backup: Some(kbupd_client::BackupRequest { + service_id, + backup_id: Some(backup_id_1), + valid_from: Some(valid_from), + token: Some(token), + data: Some(unwrap_or_random_bytes(32, backup_data)), + pin: Some(unwrap_or_random_bytes(32, backup_pin)), + tries: backup_tries, + }), + restore: None, + delete: None, + } + } + "restore" => { + kbupd_client::Request { + backup: None, + restore: Some(kbupd_client::RestoreRequest { + service_id, + backup_id: Some(backup_id_1), + token: Some(token), + valid_from: Some(valid_from), + pin: Some(unwrap_or_random_bytes(32, backup_pin)), + }), + delete: None, + } + } + _ => Default::default(), + }; + request + }) + .and_then(move |request: kbupd_client::Request| { + debug!("negotiating request {}", request_id); + negotiate_client_request(request_tx, enclave_name, request_id, client_pubkey) + .map(move |negotiation: kbupd_client::RequestNegotiation| { + (request, negotiation) + }) + }) + .and_then(move |(request, negotiation): (kbupd_client::Request, kbupd_client::RequestNegotiation)| { + if let Some(backup_request) = &request.backup { + info!("sending backup request {}: {}", request_id, backup_request); + } + if let Some(restore_request) = &request.restore { + info!("sending restore request {}: {}", request_id, restore_request); + } + if let Some(delete_request) = &request.delete { + info!("sending delete request {}: {}", request_id, delete_request); + } + send_client_request(request_tx_1, enclave_name_1, request_id, backup_id, client, random, negotiation, request) + }) + .map(move |response: kbupd_client::Response| { + if let Some(backup_response) = &response.backup { + println!("{}", &backup_response); + } + if let Some(restore_response) = &response.restore { + println!("{}", &restore_response); + } + }) +} + +fn send_transaction_request(request_tx: ControlClientSender, + enclave_name: String, + request_id: u64, + data: transaction_control_request::Data) + -> impl Future +{ + let control_request = ControlRequest { + id: request_id, + data: Some(control_request::Data::TransactionControlRequest(TransactionControlRequest { + enclave_name, + data: Some(data), + })), + }; + (request_tx.call(control_request)) + .and_then(move |reply: ControlReply| { + if let Some(control_reply::Data::TransactionControlReply(transaction_control_reply)) = reply.data { + Ok(transaction_control_reply) + } else { + error!("error parsing reply {}: {:?}", request_id, reply); + Err(()) + } + }) +} + +fn negotiate_client_request(request_tx: ControlClientSender, + enclave_name: String, + request_id: u64, + client_pubkey: Vec) + -> impl Future +{ + let control_request = ControlRequest { + id: request_id, + data: Some(control_request::Data::NegotiateClientRequest(NegotiateClientRequest { + enclave_name, + client_pubkey, + })), + }; + + (request_tx.call(control_request)) + .and_then(move |reply: ControlReply| { + parse_negotiate_client_reply(reply) + .map_err(|error: failure::Error| { + error!("error parsing reply {}: {}", request_id, error); + }) + }) +} + +fn parse_negotiate_client_reply(reply: ControlReply) -> Result { + match reply.data { + Some(control_reply::Data::NegotiateClientReply(NegotiateClientReply { + server_static_pubkey, + server_ephemeral_pubkey, + encrypted_pending_request_id, + })) => { + Ok(kbupd_client::RequestNegotiation { + server_static_pubkey: server_static_pubkey[..].try_into().map_err(failure::Error::from).context("bad client request negotiation reply server_static_pubkey")?, + server_ephemeral_pubkey: server_ephemeral_pubkey[..].try_into().map_err(failure::Error::from).context("bad client request negotiation reply server_ephemeral_pubkey")?, + encrypted_pending_request_id: kbupd_client::EncryptedMessage { + iv: encrypted_pending_request_id.iv[..].try_into().map_err(failure::Error::from).context("bad client request negotiation reply encrypted_pending_request_id.iv")?, + mac: encrypted_pending_request_id.mac[..].try_into().map_err(failure::Error::from).context("bad client request negotiation reply encrypted_pending_request_id.mac")?, + data: encrypted_pending_request_id.data, + }, + }) + } + _ => { + Err(failure::format_err!("bad client negotiation reply: {:?}", reply.data)) + } + } +} + +fn send_client_request(request_tx: ControlClientSender, + enclave_name: String, + request_id: u64, + backup_id: Vec, + client: kbupd_client::Client, + mut random: OsRng, + negotiation: kbupd_client::RequestNegotiation, + request: kbupd_client::Request) + -> impl Future +{ + let request_type = match &request { + kbupd_client::Request { backup: Some(_), .. } => ClientRequestType::Backup, + kbupd_client::Request { restore: Some(_), .. } => ClientRequestType::Restore, + kbupd_client::Request { delete: Some(_), .. } => ClientRequestType::Delete, + _ => unreachable!(), + }; + client.request(&mut random, negotiation, request) + .into_future() + .map_err(move |error| { + error!("error building client request {}: {}", request_id, error); + }) + .and_then(move |(encrypted_request, pending_request): (kbupd_client::EncryptedRequest, kbupd_client::PendingRequest)| { + let control_request = ControlRequest { + id: request_id, + data: Some(control_request::Data::ClientEncryptedRequest(ClientEncryptedRequest { + enclave_name, + request_type: request_type.into(), + backup_id, + pending_request_id: encrypted_request.pending_request_id, + encrypted_message: ClientEncryptedMessage { + iv: encrypted_request.encrypted_message.iv.to_vec(), + mac: encrypted_request.encrypted_message.mac.to_vec(), + data: encrypted_request.encrypted_message.data, + }, + })), + }; + + request_tx.call(control_request) + .map(|reply: ControlReply| (reply, pending_request)) + }) + .and_then(move |(reply, pending_request): (ControlReply, kbupd_client::PendingRequest)| { + parse_client_reply(reply) + .map_err(|error: failure::Error| { + error!("error parsing client reply {}: {}", request_id, error); + }) + .map(|encrypted_message: kbupd_client::EncryptedMessage| (encrypted_message, pending_request)) + }) + .and_then(move |(encrypted_message, pending_request): (kbupd_client::EncryptedMessage, kbupd_client::PendingRequest)| { + pending_request + .decrypt_reply(encrypted_message) + .map(|response: kbupd_client::Response| response) + .map_err(|error| { + error!("error decrypting client response {}: {}", request_id, error); + }) + }) +} + +fn parse_client_reply(reply: ControlReply) -> Result { + match reply.data { + Some(control_reply::Data::ClientEncryptedReply(ClientEncryptedReply { encrypted_message })) => { + Ok(kbupd_client::EncryptedMessage { + iv: encrypted_message.iv[..].try_into().map_err(failure::Error::from).context("bad client reply iv")?, + mac: encrypted_message.mac[..].try_into().map_err(failure::Error::from).context("bad client reply mac")?, + data: encrypted_message.data, + }) + } + _ => { + Err(failure::format_err!("bad client reply: {:?}", reply.data)) + } + } +} + +struct ControlClientState { + framed_tx: SplitSink, + requests: BTreeMap>, +} + +#[derive(Clone)] +struct ControlClientSender { + request_tx: mpsc::UnboundedSender<(ControlRequest, oneshot::Sender)>, +} +impl ControlClientSender { + fn call(&self, request: ControlRequest) -> impl Future { + let (reply_tx, reply_rx) = oneshot::channel(); + let _ignore = self.request_tx.unbounded_send((request, reply_tx)); + reply_rx.map_err(|oneshot::Canceled| ()) + } +} + +enum ControlMessage { + Request(ControlRequest, oneshot::Sender), + Reply(ControlReply), +} + +fn control_client(framed: ControlFramed, + request_rx: mpsc::UnboundedReceiver<(ControlRequest, oneshot::Sender)>) + -> impl Future { + let (framed_tx, framed_rx) = framed.split(); + let state = ControlClientState { + framed_tx, + requests: Default::default(), + }; + framed_rx + .map(|reply: ControlReply| ControlMessage::Reply(reply)) + .select(request_rx.map(|(request, reply_tx): (ControlRequest, oneshot::Sender)| ControlMessage::Request(request, reply_tx)).map_err(|()| unreachable!())) + .fold(state, |mut state: ControlClientState, message: ControlMessage| { + match message { + ControlMessage::Request(request, reply_tx) => { + let ControlClientState { framed_tx, mut requests } = state; + let request_id = request.id; + if let Some(_) = requests.insert(request.id, reply_tx) { + warn!("dropping pending request {}!", request_id); + } + let future = framed_tx + .send(request) + .map(move |framed_tx: SplitSink| { + ControlClientState { framed_tx, requests } + }); + future::Either::A(future) + } + ControlMessage::Reply(reply) => { + if let Some(reply_tx) = state.requests.remove(&reply.id) { + let _ignore = reply_tx.send(reply); + } else { + warn!("dropping reply {}!", reply.id); + } + future::Either::B(Ok(state).into_future()) + } + } + }) + .map_err(|error: io::Error| { + error!("error receiving replies: {}", error); + }) + .map(|_| ()) +} + +fn daemon_call(framed: ControlFramed, request: ControlRequest) + -> impl Future +{ + framed.send(request) + .and_then(|framed: ControlFramed| { + framed.into_future() + .map_err(|(error, _framed): (io::Error, ControlFramed)| error) + .and_then(|(reply, framed): (Option, ControlFramed)| { + reply.ok_or(io::ErrorKind::UnexpectedEof.into()) + .map(|reply: ControlReply| (reply, framed)) + }) + }) +} + +fn parse_arguments() -> clap::ArgMatches<'static> { + let enclave_name_argument = + clap::Arg::with_name("enclave_name") + .takes_value(true) + .long("enclave-name") + .value_name("enclave_name") + .help("Name of enclave to query, in a frontend kbupd instance"); + + let info_subcommand = + clap::SubCommand::with_name("info") + .arg(enclave_name_argument.clone()) + .about("query and output basic information like node ID, service ID, and partition ID in a parsable format"); + + let detailed_memory_status_argument = + clap::Arg::with_name("detailed_memory_status") + .long("detailed-memory-status") + .help("run mallinfo in enclave to traverse all free memory blocks to calculate used memory"); + + let status_subcommand = + clap::SubCommand::with_name("status") + .arg(enclave_name_argument.clone()) + .arg(detailed_memory_status_argument) + .about("dump complete status information in a human-readable format"); + + let metrics_subcommand = + clap::SubCommand::with_name("metrics") + .about("dump a metrics snapshot in JSON"); + + let xfer_start_subcommand = + clap::SubCommand::with_name("start") + .about("start an incoming transfer from a source partition described in the config this partition was started with"); + + let xfer_pause_subcommand = + clap::SubCommand::with_name("pause") + .about("pause an ongoing outgoing transfer"); + + let xfer_resume_subcommand = + clap::SubCommand::with_name("resume") + .about("resume a paused outgoing transfer"); + + let xfer_finish_subcommand = + clap::SubCommand::with_name("finish") + .about("finish a transfer, tearing down its state, allowing acceptance of a new outgoing transfer"); + + let xfer_subcommand = + clap::SubCommand::with_name("xfer") + .setting(clap::AppSettings::VersionlessSubcommands) + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .subcommand(xfer_start_subcommand) + .subcommand(xfer_pause_subcommand) + .subcommand(xfer_resume_subcommand) + .subcommand(xfer_finish_subcommand) + .arg(enclave_name_argument.clone()) + .about("control inter-partition key-space and data transfer"); + + let peer_node_id_argument = + clap::Arg::with_name("peer_node_id") + .required(true) + .takes_value(true) + .long("peer-node-id") + .value_name("node_id_hex") + .help("Node ID of peer to force a reconnect to, as a hexadecimal byte string"); + + let peer_address_argument = + clap::Arg::with_name("peer_address") + .takes_value(true) + .long("peer-address") + .value_name("address") + .help("ip[:port] address of peer to reconnect to"); + + let reconnect_peer_subcommand = + clap::SubCommand::with_name("reconnect-peer") + .arg(enclave_name_argument.clone()) + .arg(peer_node_id_argument.clone()) + .arg(peer_address_argument) + .about("force reconnection to a specified peer"); + + let disconnect_peer_subcommand = + clap::SubCommand::with_name("disconnect-peer") + .arg(enclave_name_argument.clone()) + .arg(peer_node_id_argument) + .about("force disconnection from a specified peer"); + + let backup_id_argument = + clap::Arg::with_name("backup_id") + .takes_value(true) + .long("backup-id") + .value_name("backup_id_hex") + .help("Backup ID to use in client request, as a hexadecimal byte string. Randomly generated by default."); + + let request_count_argument = + clap::Arg::with_name("request_count") + .takes_value(true) + .default_value("1") + .long("request-count") + .value_name("request_count") + .help("Number of requests to perform, in decimal. For request parameters randomized by default, different values will be generated for each request."); + + let max_parallel_argument = + clap::Arg::with_name("max_parallel") + .takes_value(true) + .long("max-parallel") + .value_name("max_parallel") + .help("Maximum number of requests to perform in parallel, in decimal."); + + let client_create_subcommand = + clap::SubCommand::with_name("create") + .arg(backup_id_argument.clone()) + .arg(request_count_argument.clone()) + .arg(max_parallel_argument.clone()) + .about("Key Backup Service Client - Create"); + + let client_delete_subcommand = + clap::SubCommand::with_name("delete") + .arg(backup_id_argument.clone()) + .arg(request_count_argument.clone()) + .arg(max_parallel_argument.clone()) + .about("Key Backup Service Client - Delete"); + + let service_id_argument = + clap::Arg::with_name("service_id") + .takes_value(true) + .long("service-id") + .value_name("service_id_hex") + .help("Service ID to use in client request, as a hexadecimal byte string"); + + let request_token_argument = + clap::Arg::with_name("request_token") + .takes_value(true) + .long("request-token") + .value_name("request_token_hex") + .help("Token to use in client request, as a hexadecimal byte string"); + + let request_valid_from_argument = + clap::Arg::with_name("request_valid_from") + .takes_value(true) + .long("request-valid-from") + .value_name("seconds_since_unix_epoch") + .help("Time after which this request is valid, in seconds since unix epoch"); + + let backup_data_argument = + clap::Arg::with_name("backup_data") + .takes_value(true) + .long("backup-data") + .value_name("backup_data_hex") + .help("Backup Key to use in client request, as a hexadecimal byte string. Randomly generated by default."); + + let backup_pin_argument = + clap::Arg::with_name("backup_pin") + .takes_value(true) + .long("backup-pin") + .value_name("backup_pin_hex") + .help("Backup PIN to use in client request, as a hexadecimal byte string. Randomly generated by default."); + + let backup_tries_argument = + clap::Arg::with_name("backup_tries") + .takes_value(true) + .default_value("10") + .long("backup-tries") + .value_name("backup_tries") + .help("Backup try count to use in client request, in decimal"); + + let client_backup_subcommand = + clap::SubCommand::with_name("backup") + .arg(service_id_argument.clone()) + .arg(backup_id_argument.clone()) + .arg(request_token_argument.clone()) + .arg(request_valid_from_argument.clone()) + .arg(request_count_argument.clone()) + .arg(max_parallel_argument.clone()) + .arg(backup_data_argument) + .arg(backup_pin_argument.clone()) + .arg(backup_tries_argument) + .about("Key Backup Service Client - Backup"); + + let client_restore_subcommand = + clap::SubCommand::with_name("restore") + .arg(service_id_argument) + .arg(backup_id_argument) + .arg(request_token_argument) + .arg(request_valid_from_argument) + .arg(request_count_argument) + .arg(max_parallel_argument) + .arg(backup_pin_argument) + .about("Key Backup Service Client - Restore"); + + let client_subcommand = + clap::SubCommand::with_name("client") + .setting(clap::AppSettings::VersionlessSubcommands) + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .subcommand(client_create_subcommand) + .subcommand(client_delete_subcommand) + .subcommand(client_backup_subcommand) + .subcommand(client_restore_subcommand) + .arg(enclave_name_argument.required(true)) + .about("Key Backup Service Client"); + + let connect_argument = + clap::Arg::with_name("connect_address") + .takes_value(true) + .default_value("127.0.0.1:31338") + .long("connect") + .value_name("connect_address") + .help("ip[:port] address of kbupd to connect to"); + + let debug_argument = + clap::Arg::with_name("debug") + .long("debug") + .help("emit debug logging"); + + clap::App::new("kbupctl") + .version(clap::crate_version!()) + .about(format!("{} -- Control Utility", clap::crate_description!()).as_str()) + .author(clap::crate_authors!()) + .setting(clap::AppSettings::VersionlessSubcommands) + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .arg(connect_argument) + .arg(debug_argument) + .subcommand(client_subcommand) + .subcommand(info_subcommand) + .subcommand(status_subcommand) + .subcommand(metrics_subcommand) + .subcommand(reconnect_peer_subcommand) + .subcommand(disconnect_peer_subcommand) + .subcommand(xfer_subcommand) + .get_matches() +} diff --git a/service/kbupd/src/constants.rs b/service/kbupd/src/constants.rs new file mode 100644 index 0000000..cd679ae --- /dev/null +++ b/service/kbupd/src/constants.rs @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub const METRICS_NAME: &'static str = "signal"; diff --git a/service/kbupd/src/control.rs b/service/kbupd/src/control.rs new file mode 100644 index 0000000..f3f4336 --- /dev/null +++ b/service/kbupd/src/control.rs @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::convert::{TryInto}; +use std::io; +use std::marker::{PhantomData}; +use std::net::{ToSocketAddrs}; +use std::time::{Duration}; + +use bytes::{Buf, BufMut, BytesMut, IntoBuf}; +use failure::{ResultExt}; +use futures::future; +use futures::sync::mpsc; +use futures::prelude::*; +use kbupd_api::entities::*; +use tk_listen::{ListenExt}; +use tokio::net::{TcpListener, TcpStream}; +use tokio_codec::{Decoder}; + +use crate::*; +use crate::metrics::*; +use crate::peer::manager::*; +use crate::protobufs::kbupd::*; + +const LISTEN_RETRY_DELAY: Duration = Duration::from_secs(5); + +pub struct ControlListener { + enclave_tx: EnclaveManagerSender, + listener: TcpListener, +} + +impl ControlListener { + pub fn new(bind_address: impl ToSocketAddrs, enclave_tx: EnclaveManagerSender) -> io::Result { + let listener = TcpListener::bind(&util::to_socket_addr(bind_address)?)?; + Ok(Self { + enclave_tx, + listener, + }) + } + pub fn into_future(self) -> impl Future { + let Self { enclave_tx, listener } = self; + let connections = listener.incoming().sleep_on_error(LISTEN_RETRY_DELAY); + + connections.for_each(move |stream: TcpStream| { + match stream.peer_addr() { + Ok(socket_addr) => info!("accepted control connection from {}", socket_addr), + Err(_) => info!("accepted control connection from unknown address"), + } + + let _ignore = stream.set_nodelay(true); + + tokio::spawn(ControlConnection { + enclave_tx: enclave_tx.clone(), + stream, + }.into_future()); + Ok(()) + }) + } +} + +pub struct ControlConnection { + enclave_tx: EnclaveManagerSender, + stream: TcpStream, +} + +impl ControlConnection { + pub fn into_future(self) -> impl Future { + let Self { enclave_tx, stream } = self; + + let stream_peer_addr = match stream.peer_addr() { + Ok(socket_addr) => socket_addr.to_string(), + Err(_) => "unknown_address".to_string(), + }; + + let (sink, stream) = DaemonControlCodec::new().framed(stream).split(); + let (sink_tx, sink_rx) = mpsc::unbounded(); + + let sink_future = sink_rx.map_err(|()| io::Error::from(io::ErrorKind::BrokenPipe)) + .forward(sink) + .map(|_| ()); + + let stream_future = stream.for_each(move |request: ControlRequest| { + let reply_tx = sink_tx.clone(); + let future = handle_control_command(request, &enclave_tx) + .map(move |reply: ControlReply| { + let _ignore = reply_tx.unbounded_send(reply); + }); + tokio::spawn(future); + Ok(()) + }); + + let joined_future = sink_future.join(stream_future); + + joined_future.then(move |result: Result<_, io::Error>| { + if let Err(error) = result { + warn!("error in control connection at {}: {}", stream_peer_addr, error); + } else { + info!("control connection closed at {}", stream_peer_addr); + } + Ok(()) + }) + } +} + +fn handle_control_command(request: ControlRequest, + enclave_tx: &EnclaveManagerSender) + -> Box + Send> { + let request_id = request.id; + match request.data { + Some(control_request::Data::GetStatusControlRequest(request)) => { + let result = enclave_tx.call(|enclave_manager: &mut EnclaveManager, reply_tx| { + enclave_manager.get_status(request, reply_tx) + }); + + let reply = result.then(move |result: Result| { + let reply_data = match result { + Ok(reply) => { + control_reply::Data::GetStatusControlReply(reply) + } + Err(error) => { + control_reply::Data::ControlErrorSignal(ControlErrorSignal { + reason: format!("{}", error), + }) + } + }; + Ok(ControlReply { + id: request_id, + data: Some(reply_data), + }) + }); + + Box::new(reply) + } + Some(control_request::Data::NegotiateClientRequest(mut negotiate_client_request)) => { + debug!("got negotiate client request: {:?}", negotiate_client_request); + + let enclave_name = std::mem::replace(&mut negotiate_client_request.enclave_name, String::new()); + + let request = match into_attestation_request(negotiate_client_request) { + Ok(request) => request, + Err(error) => { + let reply = ControlReply { + id: request_id, + data: Some(control_reply::Data::ControlErrorSignal(ControlErrorSignal { + reason: format!("{}", error), + })), + }; + return Box::new(Ok(reply).into_future()); + } + }; + + let result = enclave_tx.call(|enclave_manager: &mut EnclaveManager, reply_tx| { + enclave_manager.remote_attestation(enclave_name, request, reply_tx) + }); + let reply = result.then(move |result: Result| { + let reply_data = match result { + Ok(response) => { + control_reply::Data::NegotiateClientReply(NegotiateClientReply { + server_ephemeral_pubkey: response.serverEphemeralPublic.to_vec(), + server_static_pubkey: response.serverStaticPublic.to_vec(), + encrypted_pending_request_id: ClientEncryptedMessage { + iv: response.iv.to_vec(), + mac: response.tag.to_vec(), + data: response.ciphertext, + }, + }) + } + Err(error) => { + control_reply::Data::ControlErrorSignal(ControlErrorSignal { + reason: format!("{}", error), + }) + } + }; + Ok(ControlReply { + id: request_id, + data: Some(reply_data), + }) + }); + + Box::new(reply) + } + Some(control_request::Data::ClientEncryptedRequest(mut client_encrypted_request)) => { + debug!("got client request: {:?}", client_encrypted_request); + + let enclave_name = std::mem::replace(&mut client_encrypted_request.enclave_name, String::new()); + let backup_id = match client_encrypted_request.backup_id[..].try_into() { + Ok(backup_id) => backup_id, + Err(_) => { + let reply = ControlReply { + id: request_id, + data: Some(control_reply::Data::ControlErrorSignal(ControlErrorSignal { + reason: format!("invalid backup id: {}", util::ToHex(&client_encrypted_request.backup_id)), + })), + }; + return Box::new(Ok(reply).into_future()); + } + }; + + let request = match parse_client_request(client_encrypted_request) { + Ok(request) => request, + Err(error) => { + let reply = ControlReply { + id: request_id, + data: Some(control_reply::Data::ControlErrorSignal(ControlErrorSignal { + reason: format!("{}", error), + })), + }; + return Box::new(Ok(reply).into_future()); + } + }; + + let result = enclave_tx.call(move |enclave_manager: &mut EnclaveManager, reply_tx| { + enclave_manager.key_backup(enclave_name, backup_id, request, reply_tx) + }); + + let reply = result.then(move |result: Result| { + let reply_data = match result { + Ok(key_backup_response) => { + control_reply::Data::ClientEncryptedReply(ClientEncryptedReply { + encrypted_message: ClientEncryptedMessage { + iv: key_backup_response.iv.to_vec(), + mac: key_backup_response.mac.to_vec(), + data: key_backup_response.data, + }, + }) + } + Err(error) => { + control_reply::Data::ControlErrorSignal(ControlErrorSignal { + reason: format!("{}", error), + }) + } + }; + Ok(ControlReply { + id: request_id, + data: Some(reply_data), + }) + }); + + Box::new(reply) + } + Some(control_request::Data::TransactionControlRequest(mut transaction_control_request)) => { + debug!("got transaction control request: {:?}", transaction_control_request); + + let enclave_name = std::mem::replace(&mut transaction_control_request.enclave_name, String::new()); + + let result = if let Some(transaction_control_request_data) = transaction_control_request.data { + let untrusted_transaction_request = match transaction_control_request_data { + transaction_control_request::Data::CreateBackupRequest(request) => untrusted_transaction_request::Data::CreateBackupRequest(request), + transaction_control_request::Data::DeleteBackupRequest(request) => untrusted_transaction_request::Data::DeleteBackupRequest(request), + }; + let result = enclave_tx.call(|enclave_manager: &mut EnclaveManager, reply_tx| { + enclave_manager.transaction(enclave_name, untrusted_transaction_request, reply_tx) + }); + future::Either::A(result) + } else { + future::Either::B(Err(EnclaveTransactionError::InvalidInput).into_future()) + }; + + let reply = result.then(move |result: Result| { + let reply_data = match result { + Ok(untrusted_transaction_reply) => { + let transaction_control_reply_data = match untrusted_transaction_reply { + untrusted_transaction_reply::Data::CreateBackupReply(request) => transaction_control_reply::Data::CreateBackupReply(request), + untrusted_transaction_reply::Data::DeleteBackupReply(request) => transaction_control_reply::Data::DeleteBackupReply(request), + }; + control_reply::Data::TransactionControlReply(TransactionControlReply { + data: Some(transaction_control_reply_data), + }) + } + Err(error) => { + control_reply::Data::ControlErrorSignal(ControlErrorSignal { + reason: format!("{}", error), + }) + } + }; + Ok(ControlReply { + id: request_id, + data: Some(reply_data), + }) + }); + + Box::new(reply) + } + Some(control_request::Data::ForcePeerReconnectRequest(request)) => { + let enclave_name = request.enclave_name.unwrap_or(String::new()); + let peer_node_id = parse_node_id(&request.node_id); + let peer_address = request.address; + + let reset_peer_message = UntrustedMessage { + inner: Some(untrusted_message::Inner::ResetPeerSignal(ResetPeerSignal { + peer_node_id: request.node_id, + })) + }; + + let result = enclave_tx.call(|enclave_manager: &mut EnclaveManager, reply_tx| { + enclave_manager.untrusted_message(enclave_name.clone(), reset_peer_message)?; + enclave_manager.get_peer_manager(enclave_name, reply_tx) + }); + + let reply = result.then(move |result: Result, futures::Canceled>| { + if let Ok(Some(peer_manager_tx)) = result { + if let Some(peer_node_id) = peer_node_id { + let _ignore = peer_manager_tx.cast(move |peer_manager: &mut PeerManager| { + peer_manager.force_reconnect(peer_node_id, peer_address) + }); + } + } + Ok(ControlReply { + id: request_id, + data: None, + }) + }); + + Box::new(reply) + } + Some(control_request::Data::PeerDisconnectRequest(request)) => { + let enclave_name = request.enclave_name.unwrap_or(String::new()); + let peer_node_id = parse_node_id(&request.node_id); + + let result = enclave_tx.call(|enclave_manager: &mut EnclaveManager, reply_tx| { + enclave_manager.get_peer_manager(enclave_name, reply_tx) + }); + + let reply = result.then(move |result: Result, futures::Canceled>| { + if let Ok(Some(peer_manager_tx)) = result { + if let Some(peer_node_id) = peer_node_id { + let _ignore = peer_manager_tx.cast(move |peer_manager: &mut PeerManager| { + peer_manager.disconnect(peer_node_id) + }); + } + } + Ok(ControlReply { + id: request_id, + data: None, + }) + }); + + Box::new(reply) + } + Some(control_request::Data::PeerPermanentDeleteRequest(request)) => { + let enclave_name = request.enclave_name.unwrap_or(String::new()); + let peer_node_id = parse_node_id(&request.node_id); + + let result = enclave_tx.call(|enclave_manager: &mut EnclaveManager, reply_tx| { + enclave_manager.get_peer_manager(enclave_name, reply_tx) + }); + + let reply = result.then(move |result: Result, futures::Canceled>| { + if let Ok(Some(peer_manager_tx)) = result { + if let Some(peer_node_id) = peer_node_id { + let _ignore = peer_manager_tx.cast(move |peer_manager: &mut PeerManager| { + peer_manager.disconnect_permanently(peer_node_id) + }); + } + } + Ok(ControlReply { + id: request_id, + data: None, + }) + }); + + Box::new(reply) + } + Some(control_request::Data::XferControlRequest(xfer_control_request)) => { + info!("got xfer control request: {:?}", xfer_control_request); + + let enclave_name = xfer_control_request.enclave_name.clone().unwrap_or_default(); + let xfer_command = XferControlCommand::from_i32(xfer_control_request.xfer_control_command).unwrap_or_default(); + + let result = enclave_tx.call(move |enclave_manager: &mut EnclaveManager, reply_tx| { + enclave_manager.xfer(enclave_name, xfer_command, reply_tx) + }); + + let reply = result.then(move |result: Result| { + let reply_data = match result { + Ok(xfer_control_reply) => { + control_reply::Data::XferControlReply(xfer_control_reply) + } + Err(error) => { + control_reply::Data::ControlErrorSignal(ControlErrorSignal { + reason: format!("{}", error), + }) + } + }; + Ok(ControlReply { + id: request_id, + data: Some(reply_data), + }) + }); + + Box::new(reply) + } + Some(control_request::Data::GetMetricsControlRequest(_request)) => { + let metrics_report = MetricsReport::from(&*METRICS); + let encoded_report = match serde_json::to_string(&metrics_report) { + Ok(encoded_request) => encoded_request, + Err(serde_error) => { + warn!("error encoding json metrics: {}", serde_error); + Default::default() + } + }; + let reply_data = control_reply::Data::GetMetricsControlReply(GetMetricsControlReply { + metrics_json: encoded_report, + }); + Box::new(Ok(ControlReply { + id: request_id, + data: Some(reply_data), + }).into_future()) + } + None => { + let reply = ControlReply { + id: request_id, + data: None, + }; + Box::new(Ok(reply).into_future()) + } + } +} + +fn parse_node_id(node_id_bytes: &[u8]) -> Option { + match node_id_bytes.try_into() { + Ok(node_id) => Some(node_id), + Err(_) => { + error!("invalid node id in control request: {}", util::ToHex(node_id_bytes)); + None + } + } +} + +fn parse_client_request(request: ClientEncryptedRequest) -> Result { + Ok(KeyBackupRequest { + iv: request.encrypted_message.iv[..].try_into().map_err(failure::Error::from).context("iv")?, + mac: request.encrypted_message.mac[..].try_into().map_err(failure::Error::from).context("mac")?, + data: request.encrypted_message.data, + requestId: request.pending_request_id, + }) +} + +fn into_attestation_request(negotiate_client_request: NegotiateClientRequest) -> Result { + Ok(RemoteAttestationRequest { + clientPublic: negotiate_client_request.client_pubkey[..].try_into().map_err(failure::Error::from).context("negotiate_client client_pubkey")?, + }) +} + +type DaemonControlCodec = ControlCodec; + +pub struct ControlCodec { + _io: (PhantomData, PhantomData), +} +impl ControlCodec { + pub fn new() -> Self { + Self { + _io: Default::default(), + } + } +} +impl tokio_codec::Decoder for ControlCodec { + type Item = I; + type Error = tokio::io::Error; + fn decode(&mut self, buffer: &mut BytesMut) -> Result, Self::Error> { + if buffer.len() < 4 { + return Ok(None); + } + + let frame_length = buffer[..].into_buf().get_u32_be() as usize; + let frame_remaining = frame_length.saturating_sub(buffer.len() - 4); + if frame_remaining != 0 { + buffer.reserve(frame_remaining + 4); + return Ok(None); + } + + buffer.advance(4); + let data = buffer.split_to(frame_length); + let frame = Self::Item::decode(&data)?; + Ok(Some(frame)) + } +} +impl tokio_codec::Encoder for ControlCodec { + type Item = O; + type Error = tokio::io::Error; + fn encode(&mut self, frame: Self::Item, output: &mut BytesMut) -> Result<(), Self::Error> { + let frame_len = match frame.encoded_len() { + frame_len if frame_len > u32::max_value() as usize - 4 => { + return Err(tokio::io::Error::new(tokio::io::ErrorKind::InvalidInput, "control message size limit")); + } + frame_len => frame_len as u32, + }; + output.reserve(4 + frame_len as usize); + output.put_u32_be(frame_len); + frame.encode(output)?; + Ok(()) + } +} diff --git a/service/kbupd/src/enclave/attestation_manager.rs b/service/kbupd/src/enclave/attestation_manager.rs new file mode 100644 index 0000000..1779e4d --- /dev/null +++ b/service/kbupd/src/enclave/attestation_manager.rs @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::collections::{HashMap}; + +use futures::future; +use futures::prelude::*; +use futures::sync::oneshot; + +use crate::*; +use crate::intel_client::*; +use crate::metrics::*; +use crate::protobufs::kbupd::*; + +lazy_static::lazy_static! { + static ref GET_ATTESTATION_ATTEMPT_METER: Meter = METRICS.metric(&metric_name!("get_attestation", "attempts")); + static ref GET_ATTESTATION_OK_METER: Meter = METRICS.metric(&metric_name!("get_attestation", "ok")); + static ref GET_ATTESTATION_ERROR_METER: Meter = METRICS.metric(&metric_name!("get_attestation", "error")); +} + +pub struct AttestationManager { + enclave_tx: EnclaveManagerSender, + intel_client: Option, + requests: HashMap, oneshot::Sender>, +} + +impl AttestationManager { + pub fn new(enclave_tx: EnclaveManagerSender, + intel_client: Option) + -> Self + { + Self { + enclave_tx, + intel_client, + requests: HashMap::new(), + } + } + + pub fn get_attestation(&mut self, enclave_name: String, request: GetAttestationRequest) { + if let Some(intel_client) = &self.intel_client { + GET_ATTESTATION_ATTEMPT_METER.mark(); + + let (cancel_tx, cancel_rx) = oneshot::channel(); + + // XXX race window after cancellation? + self.requests.insert(request.request_id.clone(), cancel_tx); + + let enclave_tx = self.enclave_tx.clone(); + let request_id = request.request_id.clone(); + let request_id_2 = request.request_id; + let signed_quote = intel_client.get_quote_signature(request.sgx_quote, true); + let replied_future = signed_quote.then(move |reply: Result| { + match reply { + Ok(_) => GET_ATTESTATION_OK_METER.mark(), + Err(_) => GET_ATTESTATION_ERROR_METER.mark(), + } + // XXX clean up cancel_tx + enclave_tx.cast(move |enclave_manager: &mut EnclaveManager| { + enclave_manager.get_attestation_reply(enclave_name, request_id, reply) + }) + }); + let replied_future = replied_future.select2(cancel_rx).then(move |result: Result, future::Either<_, _>>| { + match result { + Ok(future::Either::A(((), _cancel_rx))) | Err(future::Either::A(((), _cancel_rx))) => (), + Ok(future::Either::B((_, _replied_future))) | Err(future::Either::B((oneshot::Canceled, _replied_future))) => { + debug!("canceled fetching attestation for {}", util::ToHex(&request_id_2)); + } + } + Ok(()) + }); + tokio::spawn(replied_future); + } else { + let _ignore = self.enclave_tx.cast(move |enclave_manager: &mut EnclaveManager| { + enclave_manager.get_attestation_reply(enclave_name, request.request_id, Ok(SignedQuote { + quote: request.sgx_quote, + report: Default::default(), + })) + }); + } + } +} diff --git a/service/kbupd/src/enclave/enclave.rs b/service/kbupd/src/enclave/enclave.rs new file mode 100644 index 0000000..8eba059 --- /dev/null +++ b/service/kbupd/src/enclave/enclave.rs @@ -0,0 +1,686 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::enclave::attestation_manager::AttestationManager; +use std::array::{TryFromSliceError}; +use std::collections::{HashMap}; +use std::convert::{TryFrom, TryInto}; +use std::fmt; +use std::fmt::{Display, Debug}; +use std::marker::{PhantomData}; +use std::ops::{Deref}; + +use futures::sync::oneshot; +use kbupd_api::entities::*; +use kbupd_api::entities::{BackupId}; + +use crate::*; +use crate::intel_client::*; +use crate::peer::manager::*; +use crate::protobufs::kbupd::*; +use crate::metrics::*; + +use super::ffi::ecalls; +use super::ffi::sgx::*; +use super::ffi::sgxsd::*; + +pub struct Enclave { + enclave_name: String, + enclave_id: SgxEnclaveId, + node_id: Option, + frontend_config: Option, + replica_config: Option, + status: Option, + sgx_spid: [u8; 16], + sgx_sig_rl: SignatureRevocationList, + signed_quote: Option, + server_handle: Option, + send_queue: Vec, + peer_manager_tx: actor::Sender, + attestation_tx: actor::Sender, + txn_requests: PendingRequestMap>, + xfer_requests: PendingRequestMap>, + _unsend: PhantomData<*mut u8>, +} + +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct NodeId([u8; 32]); + +pub use super::ffi::sgxsd::{SgxQuote}; + +// +// private defs +// + +struct PendingRequestMap { + last_request_id: u64, + requests: HashMap, +} + +lazy_static::lazy_static! { + static ref MEMORY_USED_GAUGE: Gauge = METRICS.metric(&metric_name!("memory", "used")); + static ref MEMORY_CHUNKS_GAUGE: Gauge = METRICS.metric(&metric_name!("memory", "chunks")); + static ref REPLICA_MIN_ATTESTATION_GAUGE: Gauge = METRICS.metric(&metric_name!("replica", "attestation", "min_timestamp")); + static ref REPLICA_TERM_GAUGE: Gauge = METRICS.metric(&metric_name!("replica", "term")); + static ref REPLICA_LOG_PREV_GAUGE: Gauge = METRICS.metric(&metric_name!("replica", "log", "prev")); + static ref REPLICA_LOG_APPLIED_METER: Meter = METRICS.metric(&metric_name!("replica", "log", "applied")); + static ref REPLICA_LOG_COMMITTED_METER: Meter = METRICS.metric(&metric_name!("replica", "log", "committed")); + static ref REPLICA_LOG_APPENDED_METER: Meter = METRICS.metric(&metric_name!("replica", "log", "appended")); + + static ref REPLICA_BACKUPS_COUNT_GAUGE: Gauge = METRICS.metric(&metric_name!("replica", "backups", "count")); + static ref REPLICA_BACKUPS_CREATE_METER: Meter = METRICS.metric(&metric_name!("replica", "backups", "create")); + static ref REPLICA_BACKUPS_BACKUP_METER: Meter = METRICS.metric(&metric_name!("replica", "backups", "backup")); + static ref REPLICA_BACKUPS_RESTORE_METER: Meter = METRICS.metric(&metric_name!("replica", "backups", "restore")); + static ref REPLICA_BACKUPS_DELETE_METER: Meter = METRICS.metric(&metric_name!("replica", "backups", "delete")); + static ref REPLICA_BACKUPS_XFER_IN_PROGRESS_METER: Meter = METRICS.metric(&metric_name!("replica", "backups", "xfer_in_progress")); + static ref REPLICA_BACKUPS_WRONG_PARTITION_METER: Meter = METRICS.metric(&metric_name!("replica", "backups", "wrong_partition")); + static ref REPLICA_BACKUPS_INVALID_REQUEST_METER: Meter = METRICS.metric(&metric_name!("replica", "backups", "invalid_request")); + static ref REPLICA_BACKUPS_INTERNAL_ERROR_METER: Meter = METRICS.metric(&metric_name!("replica", "backups", "internal_error")); + + static ref FRONTEND_REQUESTS_INFLIGHT_GAUGE: Gauge = METRICS.metric(&metric_name!("frontend", "requests", "inflight")); + static ref FRONTEND_REQUESTS_UNSENT_GAUGE: Gauge = METRICS.metric(&metric_name!("frontend", "requests", "unsent")); +} + +// +// Enclave impls +// + +impl Enclave { + pub fn new(enclave_name: String, + enclave_path: &str, + enclave_debug: bool, + sgx_spid: [u8; 16], + peer_manager_tx: actor::Sender, + attestation_tx: actor::Sender) + -> Result { + let enclave_id = create_enclave(enclave_path, enclave_debug)?; + + Ok(Self { + enclave_name, + enclave_id, + node_id: Default::default(), + frontend_config: Default::default(), + replica_config: Default::default(), + status: Default::default(), + sgx_spid, + sgx_sig_rl: Default::default(), + signed_quote: Default::default(), + server_handle: Default::default(), + send_queue: Default::default(), + peer_manager_tx, + attestation_tx, + txn_requests: Default::default(), + xfer_requests: Default::default(), + _unsend: Default::default(), + }) + } + + pub fn name(&self) -> &str { + &self.enclave_name + } + + pub fn peer_manager(&self) -> &actor::Sender { + &self.peer_manager_tx + } + + pub fn start_replica(&mut self, start_message: StartReplicaRequest) -> Result { + let config = start_message.config.clone(); + self.send_queue.push(UntrustedMessage { + inner: Some(untrusted_message::Inner::StartReplicaRequest(start_message)), + }); + self.run_to_completion()?; + self.replica_config = Some(config); + self.node_id().ok_or(EnclaveError::InternalError("enclave did not reply to start request")) + } + + pub fn start_replica_group(&mut self, start_message: StartReplicaGroupRequest) -> Result<(), EnclaveError> { + self.send_queue.push(UntrustedMessage { + inner: Some(untrusted_message::Inner::StartReplicaGroupRequest(start_message)), + }); + self.run_to_completion() + } + + pub fn start_frontend(&mut self, start_message: StartFrontendRequest, pending_requests_table_order: u8) + -> Result + { + sgxsd_node_init(self.enclave_id, pending_requests_table_order)?; + + let config = start_message.config.clone(); + self.send_queue.push(UntrustedMessage { + inner: Some(untrusted_message::Inner::StartFrontendRequest(start_message)), + }); + self.run_to_completion()?; + self.frontend_config = Some(config); + self.node_id().ok_or(EnclaveError::InternalError("enclave did not reply to start request")) + } + + pub fn node_id(&self) -> Option { + self.node_id + } + + pub fn status(&self) -> Option<&get_enclave_status_reply::Inner> { + self.status.as_ref().and_then(|status: &GetEnclaveStatusReply| status.inner.as_ref()) + } + + pub fn replica_config(&self) -> Option<&EnclaveReplicaConfig> { + self.replica_config.as_ref() + } + + pub fn frontend_config(&self) -> Option<&EnclaveFrontendConfig> { + self.frontend_config.as_ref() + } + + pub fn set_signature_revocation_list(&mut self, sig_rl: SignatureRevocationList) { + if *self.sgx_sig_rl != *sig_rl { + info!("new signature revocation list of {} bytes", sig_rl.len()); + } + self.sgx_sig_rl = sig_rl; + } + + pub fn get_next_quote(&self) -> Result { + Ok(sgxsd_get_next_quote(self.enclave_id, &self.sgx_spid, &self.sgx_sig_rl)?) + } + + pub fn set_current_quote(&mut self, signed_quote: Option) -> Result<(), EnclaveError> { + sgxsd_set_current_quote(self.enclave_id)?; + self.signed_quote = signed_quote; + Ok(()) + } + + pub fn negotiate_client(&self, request: &RemoteAttestationRequest) -> Result { + let sgxsd_request = SgxsdRequestNegotiationRequest { + client_pubkey: SgxsdCurve25519PublicKey { x: request.clientPublic }, + }; + let sgxsd_resp = sgxsd_negotiate_request(self.enclave_id, &sgxsd_request)?; + Ok(RemoteAttestationResponse { + serverStaticPublic: sgxsd_resp.server_static_pubkey.x, + serverEphemeralPublic: sgxsd_resp.server_ephemeral_pubkey.x, + + iv: sgxsd_resp.encrypted_pending_request_id.iv.data, + tag: sgxsd_resp.encrypted_pending_request_id.mac.data, + ciphertext: sgxsd_resp.encrypted_pending_request_id.data.to_vec(), + + quote: self.signed_quote.as_ref().map(|signed_quote| signed_quote.quote.clone()).unwrap_or_default(), + certificates: self.signed_quote.as_ref().map(|signed_quote| util::pem::encode("CERTIFICATE", signed_quote.report.certificates.iter().map(|certificate| &certificate[..]))).unwrap_or_default(), + signature: self.signed_quote.as_ref().map(|signed_quote| signed_quote.report.signature.clone()).unwrap_or_default(), + signatureBody: self.signed_quote.as_ref().map(|signed_quote| String::from_utf8_lossy(&signed_quote.report.body).to_string()).unwrap_or_default(), + }) + } + + pub fn start_sgxsd_server(&mut self) -> Result { + if let Some(server_handle) = self.server_handle { + Ok(server_handle) + } else { + let server_handle: SgxsdServerHandle = 0; + + let enclave_messages = sgxsd_server_start(self.enclave_id, server_handle)?; + self.server_handle = Some(server_handle); + self.handle_enclave_messages(enclave_messages); + + Ok(server_handle) + } + } + + pub fn client_request(&mut self, + backup_id: BackupId, + request: KeyBackupRequest, + reply_tx: oneshot::Sender>) + -> Result<(), EnclaveError> { + let server_handle = self.start_sgxsd_server()?; + + let args = SgxsdServerCallArgs { + request_type: KBUPD_REQUEST_TYPE_ANY, + backup_id: backup_id.into(), + }; + + let mut sgxsd_message_header = SgxsdMessageHeader { + iv: SgxsdAesGcmIv { data: request.iv }, + mac: SgxsdAesGcmMac { data: request.mac }, + pending_request_id: Default::default(), + }; + + Some(&request.requestId[..]) + .and_then(decode_field(&mut sgxsd_message_header.pending_request_id.data)) + .and_then(decode_field(&mut sgxsd_message_header.pending_request_id.iv.data)) + .and_then(decode_field(&mut sgxsd_message_header.pending_request_id.mac.data)); + + let sgxsd_reply_fun = move |result: SgxsdResult| { + let reply = result.map(|sgxsd_reply: MessageReply| { + KeyBackupResponse { + iv: sgxsd_reply.iv.data, + mac: sgxsd_reply.mac.data, + data: sgxsd_reply.data, + } + }).map_err(|error: SgxsdError| error.into()); + let _ignore = reply_tx.send(reply); + }; + + let enclave_messages = sgxsd_server_call(self.enclave_id, args, &sgxsd_message_header, &request.data, sgxsd_reply_fun, server_handle)?; + self.handle_enclave_messages(enclave_messages); + Ok(()) + } + + pub fn transaction_request(&mut self, + data: untrusted_transaction_request::Data, + reply_fun: impl FnOnce(untrusted_transaction_reply::Data) + Send + 'static) + -> Result<(), EnclaveError> { + let request_id = self.txn_requests.push(Box::new(reply_fun)); + self.enqueue_message(UntrustedMessage { + inner: Some(untrusted_message::Inner::UntrustedTransactionRequest(UntrustedTransactionRequest { + request_id, + data: Some(data), + })), + }); + self.run_to_completion() + } + + pub fn xfer_request(&mut self, + data: untrusted_xfer_request::Data, + reply_fun: impl FnOnce(UntrustedXferReply) + Send + 'static) -> Result<(), EnclaveError> { + let request_id = self.xfer_requests.push(Box::new(reply_fun)); + self.enqueue_message(UntrustedMessage { + inner: Some(untrusted_message::Inner::UntrustedXferRequest(UntrustedXferRequest { + request_id, + data: Some(data), + })), + }); + self.run_to_completion() + } + + pub fn enqueue_message(&mut self, message: UntrustedMessage) { + self.send_queue.push(message); + } + + pub fn run_to_completion(&mut self) -> Result<(), EnclaveError> { + if self.send_queue.is_empty() { + self.send_queue.push(Default::default()); + } + loop { + let messages = std::mem::replace(&mut self.send_queue, Default::default()); + if messages.is_empty() { + return Ok(()); + } + + let enclave_messages = ecalls::kbupd_send(self.enclave_id, messages)?; + self.handle_enclave_messages(enclave_messages); + }; + } + + pub fn handle_enclave_messages(&mut self, enclave_messages: impl IntoIterator) { + let enclave_message_inners = enclave_messages.into_iter() + .filter_map(|enclave_message| enclave_message.inner); + for enclave_message_inner in enclave_message_inners { + self.handle_enclave_message(enclave_message_inner); + } + } + + pub fn handle_enclave_message(&mut self, enclave_message: enclave_message::Inner) { + match enclave_message { + enclave_message::Inner::StartFrontendReply(reply) => { + match NodeId::try_from(&reply.node_id[..]) { + Ok(node_id) => { + info!("started frontend {}", &node_id); + self.node_id = Some(node_id); + } + Err(_) => { + error!("invalid frontend node id: {}", util::ToHex(&reply.node_id)); + } + }; + } + enclave_message::Inner::StartReplicaReply(reply) => { + match NodeId::try_from(&reply.node_id[..]) { + Ok(node_id) => { + info!("started replica {}", &node_id); + self.node_id = Some(node_id); + } + Err(_) => { + error!("invalid replica node id: {}", util::ToHex(&reply.node_id)); + } + } + } + enclave_message::Inner::StartReplicaGroupReply(reply) => { + if let Some(service_id) = &reply.service_id { + info!("started service {}", util::ToHex(&service_id.id)); + } + if let Some(group_id) = &reply.group_id { + info!("started partition {}", util::ToHex(group_id)); + } + } + enclave_message::Inner::SendMessageRequest(request) => { + let _ignore = self.peer_manager_tx.cast(|peer_manager: &mut PeerManager| { + peer_manager.send_message(request) + }); + } + enclave_message::Inner::GetQeInfoRequest(request) => { + self.handle_get_qe_info_request(request) + } + enclave_message::Inner::GetQuoteRequest(request) => { + self.handle_get_quote_request(request) + } + enclave_message::Inner::GetAttestationRequest(request) => { + self.handle_get_attestation_request(request) + } + enclave_message::Inner::UntrustedTransactionReply(reply) => { + self.handle_untrusted_transaction_reply(reply) + } + enclave_message::Inner::UntrustedXferReply(reply) => { + self.handle_untrusted_xfer_reply(reply) + } + enclave_message::Inner::GetEnclaveStatusReply(reply) => { + self.handle_get_enclave_status_reply(reply) + } + enclave_message::Inner::EnclaveLogSignal(enclave_log) => { + let level = match EnclaveLogLevel::from_i32(enclave_log.level) { + Some(EnclaveLogLevel::Error) => log::Level::Error, + Some(EnclaveLogLevel::Warn) => log::Level::Warn, + Some(EnclaveLogLevel::Info) => log::Level::Info, + Some(EnclaveLogLevel::Debug) => log::Level::Debug, + None => log::Level::Error, + }; + log::logger().log( + &log::RecordBuilder::new() + .args(format_args!("{}", String::from_utf8_lossy(&enclave_log.message))) + .level(level) + .target("kbupd_enclave") + .module_path(std::str::from_utf8(&enclave_log.module).ok()) + .file(std::str::from_utf8(&enclave_log.file).ok()) + .line(Some(enclave_log.line)) + .build() + ) + } + enclave_message::Inner::EnclaveTransactionSignal(enclave_txn) => { + self.handle_enclave_transaction_signal(enclave_txn) + } + } + } + fn handle_get_qe_info_request(&mut self, _request: GetQeInfoRequest) { + match get_qe_target_info() { + Ok(qe_target_info) => { + self.send_queue.push(UntrustedMessage { + inner: Some(untrusted_message::Inner::GetQeInfoReply(GetQeInfoReply { + mrenclave: qe_target_info.mr_enclave.m.to_vec(), + flags: qe_target_info.attributes.flags, + xfrm: qe_target_info.attributes.xfrm, + misc_select: qe_target_info.misc_select, + config_svn: u32::from(qe_target_info.config_svn), + config_id: qe_target_info.config_id.to_vec(), + })), + }); + } + Err(err) => { + warn!("sgx get_qe_target_info error: {:?}", err); + } + } + } + + fn handle_get_quote_request(&mut self, request: GetQuoteRequest) { + match get_quote(&request.sgx_report, &self.sgx_spid, &self.sgx_sig_rl) { + Ok(sgx_quote) => { + self.send_queue.push(UntrustedMessage { + inner: Some(untrusted_message::Inner::GetQuoteReply(GetQuoteReply { + request_id: request.request_id, + sgx_quote, + })) + }); + } + Err(err) => { + warn!("sgx get_quote error: {:?}", err); + } + } + } + + fn handle_get_attestation_request(&mut self, request: GetAttestationRequest) { + info!("fetching attestation for {}", util::ToHex(&request.request_id)); + + let enclave_name = self.enclave_name.clone(); + let _ignore = self.attestation_tx.cast(move |attestation_manager: &mut AttestationManager| { + attestation_manager.get_attestation(enclave_name, request) + }); + } + + fn handle_untrusted_transaction_reply(&mut self, reply: UntrustedTransactionReply) { + if let Some(reply_fun) = self.txn_requests.remove(reply.request_id) { + if let Some(data) = reply.data { + reply_fun(data); + } + } + } + + fn handle_untrusted_xfer_reply(&mut self, reply: UntrustedXferReply) { + if let Some(reply_fun) = self.xfer_requests.remove(reply.request_id) { + reply_fun(reply); + } + } + + fn handle_get_enclave_status_reply(&mut self, reply: GetEnclaveStatusReply) { + match &reply.inner { + Some(get_enclave_status_reply::Inner::ReplicaStatus(status)) => { + if let Some(memory_status) = &status.memory_status { + MEMORY_USED_GAUGE.update( memory_status.used_bytes); + MEMORY_CHUNKS_GAUGE.update(memory_status.free_chunks); + } + if let Some(partition_status) = &status.partition { + REPLICA_MIN_ATTESTATION_GAUGE.update(partition_status.min_attestation.unix_timestamp_seconds); + REPLICA_TERM_GAUGE.update( partition_status.current_term); + REPLICA_LOG_PREV_GAUGE.update( partition_status.prev_log_index); + REPLICA_LOG_APPLIED_METER.set( partition_status.last_applied_index); + REPLICA_LOG_COMMITTED_METER.set( partition_status.commit_index); + REPLICA_LOG_APPENDED_METER.set( partition_status.last_log_index); + REPLICA_BACKUPS_COUNT_GAUGE.update( partition_status.backup_count); + } + + let partition_config = status.partition.as_ref().map(|partition: &EnclaveReplicaPartitionStatus| { + PartitionConfig { + group_id: partition.group_id.clone(), + range: partition.range.clone(), + node_ids: partition.peers.iter() + .map(|peer: &EnclavePeerStatus| peer.node_id.clone()) + .chain(self.node_id().map(Vec::from)) + .collect(), + } + }); + + let _ignore = self.peer_manager_tx.cast(move |peer_manager: &mut PeerManager| { + peer_manager.set_partition_config(partition_config) + }); + } + Some(get_enclave_status_reply::Inner::FrontendStatus(status)) => { + if let Some(memory_status) = &status.memory_status { + MEMORY_USED_GAUGE.update( memory_status.used_bytes); + MEMORY_CHUNKS_GAUGE.update(memory_status.free_chunks); + } + let mut inflight_requests: u64 = 0; + let mut unsent_requests: u64 = 0; + for partition_status in &status.partitions { + for peer in &partition_status.nodes { + inflight_requests += peer.inflight_requests; + unsent_requests += peer.unsent_requests; + } + } + FRONTEND_REQUESTS_INFLIGHT_GAUGE.update(inflight_requests); + FRONTEND_REQUESTS_UNSENT_GAUGE.update(unsent_requests); + } + None => (), + } + + self.status = Some(reply); + } + + fn handle_enclave_transaction_signal(&mut self, enclave_txn: EnclaveTransactionSignal) { + use crate::protobufs::kbupd::enclave_transaction_signal::{Transaction}; + + let mut fetch_status = false; + + if enclave_txn.log_index == 1 { + fetch_status = true; + } + + match enclave_txn.transaction { + Some(Transaction::FrontendRequest(frontend_request)) => { + if let Some(frontend_request_transaction) = frontend_request.transaction { + self.handle_enclave_frontend_request_transaction(frontend_request_transaction); + } + } + Some(Transaction::StartXfer(_)) | + Some(Transaction::SetSid(_)) | + Some(Transaction::RemoveChunk(_)) | + Some(Transaction::ApplyChunk(_)) | + Some(Transaction::PauseXfer(_)) | + Some(Transaction::ResumeXfer(_)) | + Some(Transaction::SetTime(_)) => { + fetch_status = true; + } + Some(Transaction::FinishXfer(_)) => { + let _ignore = self.peer_manager_tx.cast(|peer_manager: &mut PeerManager| peer_manager.xfer_finished()); + fetch_status = true; + } + None => (), + } + + if fetch_status { + self.enqueue_message(UntrustedMessage { + inner: Some(untrusted_message::Inner::GetEnclaveStatusRequest(GetEnclaveStatusRequest { + memory_status: false, + })) + }) + } + } + + fn handle_enclave_frontend_request_transaction(&mut self, transaction: enclave_frontend_request_transaction::Transaction) { + use crate::protobufs::kbupd::enclave_frontend_request_transaction::{Transaction}; + match transaction { + Transaction::Create(_) => { + REPLICA_BACKUPS_CREATE_METER.mark(); + } + Transaction::Backup(_) => { + REPLICA_BACKUPS_BACKUP_METER.mark(); + } + Transaction::Restore(_) => { + REPLICA_BACKUPS_RESTORE_METER.mark(); + } + Transaction::Delete(_) => { + REPLICA_BACKUPS_DELETE_METER.mark(); + } + Transaction::XferInProgress(_) => { + REPLICA_BACKUPS_XFER_IN_PROGRESS_METER.mark(); + } + Transaction::WrongPartition(_) => { + REPLICA_BACKUPS_WRONG_PARTITION_METER.mark(); + } + Transaction::InvalidRequest(_) => { + REPLICA_BACKUPS_INVALID_REQUEST_METER.mark(); + } + Transaction::InternalError(_) => { + REPLICA_BACKUPS_INTERNAL_ERROR_METER.mark(); + } + } + } +} + +// +// NodeId impls +// + +impl AsRef<[u8]> for NodeId { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsRef<[u8; 32]> for NodeId { + fn as_ref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl Deref for NodeId { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } +} + +impl TryFrom<&'_ [u8]> for NodeId { + type Error = TryFromSliceError; + fn try_from(from: &[u8]) -> Result { + Ok(Self(from.try_into()?)) + } +} + +impl From<[u8; 32]> for NodeId { + fn from(from: [u8; 32]) -> Self { + Self(from) + } +} + +impl From for Vec { + fn from(from: NodeId) -> Self { + from.to_vec() + } +} + +impl Display for NodeId { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in &self.0 { + write!(fmt, "{:02x}", byte)?; + } + Ok(()) + } +} + +impl Debug for NodeId { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(self, fmt) + } +} + +// +// PendingRequestMap impls +// + +impl Default for PendingRequestMap { + fn default() -> Self { + Self { + last_request_id: Default::default(), + requests: Default::default(), + } + } +} + +impl PendingRequestMap { + pub fn push(&mut self, request: V) -> u64 { + self.last_request_id = self.last_request_id.wrapping_add(1); + self.requests.insert(self.last_request_id.clone(), request); + self.last_request_id.clone() + } + pub fn remove(&mut self, request_id: u64) -> Option { + self.requests.remove(&request_id) + } +} + +// +// internal +// + +fn decode_field<'a>(field: &'a mut [u8]) -> impl FnOnce(&[u8]) -> Option<&[u8]> + 'a { + move |encoded: &[u8]| { + encoded.get(..field.len()).and_then(|data| { + field.copy_from_slice(data); + encoded.get(data.len()..) + }) + } +} diff --git a/service/kbupd/src/enclave/enclave_manager.rs b/service/kbupd/src/enclave/enclave_manager.rs new file mode 100644 index 0000000..e11a15e --- /dev/null +++ b/service/kbupd/src/enclave/enclave_manager.rs @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::collections::{HashMap}; +use std::sync::mpsc; + +use failure::{Fail, ResultExt}; +use futures::sync::oneshot; +use futures::prelude::*; +use kbupd_api::entities::*; +use kbupd_api::entities::{BackupId}; + +use crate::*; +use crate::enclave::enclave::{Enclave}; +use crate::intel_client::*; +use crate::peer::manager::*; +use crate::protobufs::kbupd::*; + +use super::ffi::sgxsd::*; + +type EnclaveManagerCallback = Box Result<(), failure::Error> + Send>; + +#[derive(Clone)] +pub struct EnclaveManagerSender(mpsc::Sender); +impl EnclaveManagerSender { + pub fn cast(&self, fun: F) -> Result<(), ()> + where F: FnOnce(&mut EnclaveManager) -> Result<(), FErr> + Send + 'static, + failure::Error: From, + { + self.0.send(Box::new(move |manager: &mut EnclaveManager| { + Ok(fun(manager)?) + })).map_err(|_| ()) + } + + pub fn call(&self, fun: F) -> impl Future + where T: Send + 'static, + E: From + Send + 'static, + F: FnOnce(&mut EnclaveManager, oneshot::Sender>) -> Result<(), FErr> + Send + 'static, + failure::Error: From, + { + let (tx, rx) = oneshot::channel(); + let _ignore = self.cast(move |manager: &mut EnclaveManager| { + fun(manager, tx) + }); + rx.from_err() + .and_then(|result: Result| result) + } +} + +pub struct EnclaveManagerChannel { + tx: EnclaveManagerSender, + rx: mpsc::Receiver, +} +impl EnclaveManagerChannel { + pub fn new() -> Self { + let (tx, rx) = mpsc::channel(); + let tx = EnclaveManagerSender(tx); + Self { tx, rx } + } + + pub fn sender(&self) -> &EnclaveManagerSender { + &self.tx + } +} + +pub struct EnclaveManager { + channel: EnclaveManagerChannel, + stopped: bool, + enclaves: HashMap, +} + +impl EnclaveManager { + pub fn new(channel: EnclaveManagerChannel, enclaves: impl IntoIterator) -> Self { + Self { + channel, + stopped: false, + enclaves: enclaves.into_iter().map(|enclave: Enclave| (enclave.name().to_string(), enclave)).collect(), + } + } + + pub fn run(&mut self) -> Result<(), failure::Error> { + self.stopped = false; + while let Ok(fun) = self.channel.rx.recv() { + fun(self)?; + if self.stopped { + break; + } + } + Ok(()) + } + + pub fn stop(&mut self) -> Result<(), failure::Error> { + self.stopped = true; + Ok(()) + } + + pub fn get_next_quotes(&self, + reply_tx: oneshot::Sender, failure::Error>>) + -> Result<(), EnclaveError> { + let mut quotes: Vec<(String, SgxQuote)> = Vec::with_capacity(self.enclaves.len()); + for (enclave_name, enclave) in self.enclaves.iter() { + let quote = match enclave.get_next_quote() { + Ok(quote) => quote, + Err(error) => { + let context = error.clone().context(format!("error fetching quote for enclave {}", enclave_name)); + let _ignore = reply_tx.send(Err(context.into())); + return Err(error); + } + }; + quotes.push((enclave_name.clone(), quote)); + } + let _ignore = reply_tx.send(Ok(quotes)); + Ok(()) + } + + pub fn start_replica_group(&mut self, enclave_name: &str, start_replica_group_request: StartReplicaGroupRequest) -> Result<(), EnclaveError> { + if let Some(enclave) = self.enclaves.get_mut(enclave_name) { + enclave.start_replica_group(start_replica_group_request)?; + self.refresh_status(false)?; + } + Ok(()) + } + + pub fn set_current_quote(&mut self, enclave_name: String, signed_quote: SignedQuote) -> Result<(), EnclaveError> { + if let Some(enclave) = self.enclaves.get_mut(&enclave_name) { + enclave.set_current_quote(Some(signed_quote)) + } else { + Ok(()) + } + } + + pub fn untrusted_message(&mut self, + enclave_name: impl AsRef, + message: UntrustedMessage) + -> Result<(), EnclaveError> { + if let Some(enclave) = self.enclaves.get_mut(enclave_name.as_ref()) { + enclave.enqueue_message(message); + enclave.run_to_completion()?; + } + Ok(()) + } + + pub fn get_attestation_reply(&mut self, enclave_name: String, request_id: Vec, result: Result) + -> Result<(), EnclaveError> + { + match result { + Ok(signed_quote) => { + let reply = GetAttestationReply { + request_id, + ias_report: signed_quote.report, + }; + if let Some(enclave) = self.enclaves.get_mut(&enclave_name) { + enclave.enqueue_message(UntrustedMessage { + inner: Some(untrusted_message::Inner::GetAttestationReply(reply)), + }); + enclave.run_to_completion()?; + } + } + Err(error) => { + warn!("error fetching attestation for {}: {:?}", util::ToHex(&request_id), error); + } + } + Ok(()) + } + + pub fn xfer(&mut self, + enclave_name: String, + request: XferControlCommand, + reply_tx: oneshot::Sender>) + -> Result<(), EnclaveError> { + if let Some(enclave) = self.enclaves.get_mut(&enclave_name) { + let xfer_request_data = untrusted_xfer_request::Data::XferControlCommand(request as i32); + enclave.xfer_request(xfer_request_data, move |reply: UntrustedXferReply| { + let _ignore = reply_tx.send(Ok(XferControlReply { + status: reply.status.into(), + })); + }) + } else { + let _ignore = reply_tx.send(Err(failure::format_err!("enclave not found"))); + Ok(()) + } + } + + pub fn transaction( + &mut self, + enclave_name: String, + request_data: untrusted_transaction_request::Data, + reply_tx: oneshot::Sender> + ) -> Result<(), EnclaveError> + { + if let Some(enclave) = self.enclaves.get_mut(&enclave_name) { + enclave.transaction_request(request_data, move |reply_data: untrusted_transaction_reply::Data| { + let _ignore = reply_tx.send(Ok(reply_data)); + }) + } else { + let _ignore = reply_tx.send(Err(EnclaveTransactionError::EnclaveNotFound)); + Ok(()) + } + } + + pub fn remote_attestation(&mut self, + enclave_name: String, + request: RemoteAttestationRequest, + reply_tx: oneshot::Sender>) + -> Result<(), EnclaveError> + { + if let Some(enclave) = self.enclaves.get_mut(&enclave_name) { + let result = enclave.negotiate_client(&request); + if let Err(error) = &result { + // XXX stop on some enclave errors here + if let KeyBackupError::EnclaveError(_) = KeyBackupError::from(error.clone()) { + error!("remote attestation error: {}", error); + } + } + let _ignore = reply_tx.send(result.map_err(|error| error.into())); + Ok(()) + } else { + let _ignore = reply_tx.send(Err(RemoteAttestationError::EnclaveNotFound)); + Ok(()) + } + } + + pub fn key_backup(&mut self, + enclave_name: String, + backup_id: BackupId, + request: KeyBackupRequest, + reply_tx: oneshot::Sender>) + -> Result<(), EnclaveError> { + if let Some(enclave) = self.enclaves.get_mut(&enclave_name) { + enclave.start_sgxsd_server()?; + let result = enclave.client_request(backup_id, request, reply_tx); + if let Err(error) = &result { + // XXX stop on some enclave errors here + if let KeyBackupError::EnclaveError(_) = KeyBackupError::from(error.clone()) { + error!("client request error: {}", error); + } + } + enclave.run_to_completion() + } else { + let _ignore = reply_tx.send(Err(KeyBackupError::EnclaveNotFound)); + Ok(()) + } + } + + pub fn refresh_status(&mut self, memory_status: bool) -> Result<(), EnclaveError> { + for (_enclave_name, enclave) in &mut self.enclaves { + enclave.enqueue_message(UntrustedMessage { + inner: Some(untrusted_message::Inner::GetEnclaveStatusRequest(GetEnclaveStatusRequest { + memory_status, + })) + }); + enclave.run_to_completion()?; + } + Ok(()) + } + + pub fn get_status(&mut self, + request: GetStatusControlRequest, + reply_tx: oneshot::Sender>) + -> Result<(), EnclaveError> + { + let mut enclaves = Vec::with_capacity(self.enclaves.len()); + for (enclave_name, enclave) in &mut self.enclaves { + enclave.enqueue_message(UntrustedMessage { + inner: Some(untrusted_message::Inner::GetEnclaveStatusRequest(GetEnclaveStatusRequest { + memory_status: request.memory_status, + })) + }); + enclave.run_to_completion()?; + + if let Some(node_id) = enclave.node_id() { + let config = if let Some(config) = enclave.replica_config() { + Some(enclave_status::Config::ReplicaConfig(config.clone())) + } else if let Some(config) = enclave.frontend_config() { + Some(enclave_status::Config::FrontendConfig(config.clone())) + } else { + None + }; + let status = enclave.status().map(|status: &get_enclave_status_reply::Inner| { + match status { + get_enclave_status_reply::Inner::FrontendStatus(status) => enclave_status::Status::FrontendStatus(status.clone()), + get_enclave_status_reply::Inner::ReplicaStatus(status) => enclave_status::Status::ReplicaStatus(status.clone()), + } + }); + enclaves.push(EnclaveStatus { + name: enclave_name.clone(), + node_id: node_id.to_vec(), + config, + status, + }); + } + } + let _ignore = reply_tx.send(Ok(GetStatusControlReply { enclaves })); + Ok(()) + } + + pub fn get_peer_manager(&mut self, enclave_name: String, reply_tx: oneshot::Sender, futures::Canceled>>) + -> Result<(), EnclaveError> { + let peer_manager_tx = (self.enclaves.get(&enclave_name)) + .map(|enclave: &Enclave| enclave.peer_manager().clone()); + let _ignore = reply_tx.send(Ok(peer_manager_tx)); + Ok(()) + } + + pub fn get_sgx_gid(&mut self, reply_tx: oneshot::Sender>) -> Result<(), EnclaveError> { + let gid_result = super::ffi::sgx::get_gid().context("error fetching sgx gid"); + + let _ignore = reply_tx.send(gid_result.map_err(failure::Error::from)); + Ok(()) + } + + pub fn set_signature_revocation_list(&mut self, sig_rl: SignatureRevocationList) -> Result<(), EnclaveError> { + for (_enclave_name, enclave) in &mut self.enclaves { + enclave.set_signature_revocation_list(sig_rl.clone()); + } + Ok(()) + } +} + +impl backup::BackupEnclave for EnclaveManagerSender { + fn create_backup(&self, enclave_name: String, backup_id: BackupId) + -> Box + Send> + { + let request = untrusted_transaction_request::Data::CreateBackupRequest(CreateBackupRequest { + backup_id: protobufs::kbupd::BackupId { id: backup_id.to_vec() }, + }); + let reply_data = self.call(move |manager: &mut EnclaveManager, reply_tx| { + manager.transaction(enclave_name, request, reply_tx) + }); + let reply = reply_data.and_then(|reply_data: untrusted_transaction_reply::Data| { + if let untrusted_transaction_reply::Data::CreateBackupReply(create_backup_reply) = reply_data { + Ok(create_backup_reply) + } else { + Err(EnclaveTransactionError::InternalError) + } + }); + Box::new(reply) + } + fn get_attestation(&self, enclave_name: String, request: RemoteAttestationRequest) + -> Box + Send> + { + let reply = self.call(move |manager: &mut EnclaveManager, reply_tx| { + manager.remote_attestation(enclave_name, request, reply_tx) + }); + Box::new(reply) + } + fn put_backup_request(&self, enclave_name: String, backup_id: BackupId, request: KeyBackupRequest) + -> Box + Send> + { + let reply = self.call(move |manager: &mut EnclaveManager, reply_tx| { + manager.key_backup(enclave_name, backup_id, request, reply_tx) + }); + Box::new(reply) + } +} diff --git a/service/kbupd/src/enclave/error.rs b/service/kbupd/src/enclave/error.rs new file mode 100644 index 0000000..2a7bc01 --- /dev/null +++ b/service/kbupd/src/enclave/error.rs @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fmt; + +use futures; + +use super::ffi::sgx::*; +use super::ffi::sgxsd::*; + +#[derive(Clone, failure::Fail)] +pub enum EnclaveError { + #[fail(display = "enclave sgx error: {}", _0)] + SgxsdError(#[cause] SgxsdError), + #[fail(display = "enclave internal error: {}", _0)] + InternalError(&'static str), +} + +#[derive(Clone, failure::Fail)] +pub enum EnclaveTransactionError { + #[fail(display = "enclave not found")] + EnclaveNotFound, + #[fail(display = "invalid request")] + InvalidInput, + #[fail(display = "request canceled by enclave")] + RequestCanceled, + #[fail(display = "internal error")] + InternalError, +} + +#[derive(Clone, failure::Fail)] +pub enum RemoteAttestationError { + #[fail(display = "enclave not found")] + EnclaveNotFound, + #[fail(display = "invalid request")] + InvalidInput, + #[fail(display = "enclave error: {}", _0)] + EnclaveError(#[cause] EnclaveError), + #[fail(display = "request canceled by enclave")] + RequestCanceled, +} + +#[derive(Clone, failure::Fail)] +pub enum KeyBackupError { + #[fail(display = "enclave not found")] + EnclaveNotFound, + #[fail(display = "invalid request")] + InvalidInput, + #[fail(display = "mac mismatch")] + MacMismatch, + #[fail(display = "pending request id not found")] + PendingRequestIdNotFound, + #[fail(display = "enclave error: {}", _0)] + EnclaveError(#[cause] EnclaveError), + #[fail(display = "request canceled by enclave")] + RequestCanceled, +} + +// +// EnclaveError impls +// + +impl fmt::Debug for EnclaveError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +impl From for EnclaveError { + fn from(from: SgxsdError) -> Self { + EnclaveError::SgxsdError(from) + } +} + +// +// EnclaveTransactionError impls +// + +impl fmt::Debug for EnclaveTransactionError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +impl From for EnclaveTransactionError { + fn from(_error: futures::Canceled) -> Self { + EnclaveTransactionError::RequestCanceled + } +} + +// +// RemoteAttestationError impls +// + +impl fmt::Debug for RemoteAttestationError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +impl From for RemoteAttestationError { + fn from(_error: futures::Canceled) -> Self { + RemoteAttestationError::RequestCanceled + } +} + +impl From for RemoteAttestationError { + fn from(error: SgxsdError) -> Self { + match error.status.err() { + Some(SgxError::InvalidParameter) => RemoteAttestationError::InvalidInput, + _ => RemoteAttestationError::EnclaveError(EnclaveError::SgxsdError(error)), + } + } +} + +impl From for RemoteAttestationError { + fn from(error: EnclaveError) -> Self { + match error { + EnclaveError::SgxsdError(sgxsd_error) => Self::from(sgxsd_error), + _ => RemoteAttestationError::EnclaveError(error), + } + } +} + +// +// KeyBackupError impls +// + +impl fmt::Debug for KeyBackupError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +impl From for KeyBackupError { + fn from(_error: futures::Canceled) -> Self { + KeyBackupError::RequestCanceled + } +} + +impl From for KeyBackupError { + fn from(error: SgxsdError) -> Self { + match (error.kind, error.status.err()) { + (SgxsdErrorKind::Returned, + Some(SgxError::InvalidParameter)) => KeyBackupError::InvalidInput, + (SgxsdErrorKind::Returned, + Some(SgxError::MacMismatch)) => KeyBackupError::MacMismatch, + (SgxsdErrorKind::Returned, + Some(SgxError::SgxsdPendingRequestNotFound)) => KeyBackupError::PendingRequestIdNotFound, + _ => KeyBackupError::EnclaveError(EnclaveError::SgxsdError(error)), + } + } +} + +impl From for KeyBackupError { + fn from(error: EnclaveError) -> Self { + match error { + EnclaveError::SgxsdError(sgxsd_error) => Self::from(sgxsd_error), + _ => KeyBackupError::EnclaveError(error), + } + } +} diff --git a/service/kbupd/src/enclave/ffi/bindgen_wrapper.h b/service/kbupd/src/enclave/ffi/bindgen_wrapper.h new file mode 100644 index 0000000..bb53255 --- /dev/null +++ b/service/kbupd/src/enclave/ffi/bindgen_wrapper.h @@ -0,0 +1,5 @@ +#include "kbupd_sgxsd_callbacks.h" +#include "sgxsd.h" +#include "kbupd_enclave_u.h" +#include "sgx_uae_service.h" +#include "sgx_urts.h" diff --git a/service/kbupd/src/enclave/ffi/bindgen_wrapper.rs b/service/kbupd/src/enclave/ffi/bindgen_wrapper.rs new file mode 100644 index 0000000..955a996 --- /dev/null +++ b/service/kbupd/src/enclave/ffi/bindgen_wrapper.rs @@ -0,0 +1,5613 @@ +/* automatically generated by rust-bindgen */ + +#[repr(C)] +#[derive(Default)] +pub struct __IncompleteArrayField(::std::marker::PhantomData, [T; 0]); +impl __IncompleteArrayField { + #[inline] + pub const fn new() -> Self { + __IncompleteArrayField(::std::marker::PhantomData, []) + } + #[inline] + pub unsafe fn as_ptr(&self) -> *const T { + ::std::mem::transmute(self) + } + #[inline] + pub unsafe fn as_mut_ptr(&mut self) -> *mut T { + ::std::mem::transmute(self) + } + #[inline] + pub unsafe fn as_slice(&self, len: usize) -> &[T] { + ::std::slice::from_raw_parts(self.as_ptr(), len) + } + #[inline] + pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { + ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len) + } +} +impl ::std::fmt::Debug for __IncompleteArrayField { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + fmt.write_str("__IncompleteArrayField") + } +} +impl ::std::clone::Clone for __IncompleteArrayField { + #[inline] + fn clone(&self) -> Self { + Self::new() + } +} +pub const _STDINT_H: u32 = 1; +pub const _FEATURES_H: u32 = 1; +pub const _DEFAULT_SOURCE: u32 = 1; +pub const __USE_ISOC11: u32 = 1; +pub const __USE_ISOC99: u32 = 1; +pub const __USE_ISOC95: u32 = 1; +pub const __USE_POSIX_IMPLICITLY: u32 = 1; +pub const _POSIX_SOURCE: u32 = 1; +pub const _POSIX_C_SOURCE: u32 = 200809; +pub const __USE_POSIX: u32 = 1; +pub const __USE_POSIX2: u32 = 1; +pub const __USE_POSIX199309: u32 = 1; +pub const __USE_POSIX199506: u32 = 1; +pub const __USE_XOPEN2K: u32 = 1; +pub const __USE_XOPEN2K8: u32 = 1; +pub const _ATFILE_SOURCE: u32 = 1; +pub const __USE_MISC: u32 = 1; +pub const __USE_ATFILE: u32 = 1; +pub const __USE_FORTIFY_LEVEL: u32 = 0; +pub const __GLIBC_USE_DEPRECATED_GETS: u32 = 0; +pub const _STDC_PREDEF_H: u32 = 1; +pub const __STDC_IEC_559__: u32 = 1; +pub const __STDC_IEC_559_COMPLEX__: u32 = 1; +pub const __STDC_ISO_10646__: u32 = 201706; +pub const __STDC_NO_THREADS__: u32 = 1; +pub const __GNU_LIBRARY__: u32 = 6; +pub const __GLIBC__: u32 = 2; +pub const __GLIBC_MINOR__: u32 = 27; +pub const _SYS_CDEFS_H: u32 = 1; +pub const __glibc_c99_flexarr_available: u32 = 1; +pub const __WORDSIZE: u32 = 64; +pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1; +pub const __SYSCALL_WORDSIZE: u32 = 64; +pub const __HAVE_GENERIC_SELECTION: u32 = 1; +pub const __GLIBC_USE_LIB_EXT2: u32 = 0; +pub const __GLIBC_USE_IEC_60559_BFP_EXT: u32 = 0; +pub const __GLIBC_USE_IEC_60559_FUNCS_EXT: u32 = 0; +pub const __GLIBC_USE_IEC_60559_TYPES_EXT: u32 = 0; +pub const _BITS_TYPES_H: u32 = 1; +pub const _BITS_TYPESIZES_H: u32 = 1; +pub const __OFF_T_MATCHES_OFF64_T: u32 = 1; +pub const __INO_T_MATCHES_INO64_T: u32 = 1; +pub const __RLIM_T_MATCHES_RLIM64_T: u32 = 1; +pub const __FD_SETSIZE: u32 = 1024; +pub const _BITS_WCHAR_H: u32 = 1; +pub const _BITS_STDINT_INTN_H: u32 = 1; +pub const _BITS_STDINT_UINTN_H: u32 = 1; +pub const INT8_MIN: i32 = -128; +pub const INT16_MIN: i32 = -32768; +pub const INT32_MIN: i32 = -2147483648; +pub const INT8_MAX: u32 = 127; +pub const INT16_MAX: u32 = 32767; +pub const INT32_MAX: u32 = 2147483647; +pub const UINT8_MAX: u32 = 255; +pub const UINT16_MAX: u32 = 65535; +pub const UINT32_MAX: u32 = 4294967295; +pub const INT_LEAST8_MIN: i32 = -128; +pub const INT_LEAST16_MIN: i32 = -32768; +pub const INT_LEAST32_MIN: i32 = -2147483648; +pub const INT_LEAST8_MAX: u32 = 127; +pub const INT_LEAST16_MAX: u32 = 32767; +pub const INT_LEAST32_MAX: u32 = 2147483647; +pub const UINT_LEAST8_MAX: u32 = 255; +pub const UINT_LEAST16_MAX: u32 = 65535; +pub const UINT_LEAST32_MAX: u32 = 4294967295; +pub const INT_FAST8_MIN: i32 = -128; +pub const INT_FAST16_MIN: i64 = -9223372036854775808; +pub const INT_FAST32_MIN: i64 = -9223372036854775808; +pub const INT_FAST8_MAX: u32 = 127; +pub const INT_FAST16_MAX: u64 = 9223372036854775807; +pub const INT_FAST32_MAX: u64 = 9223372036854775807; +pub const UINT_FAST8_MAX: u32 = 255; +pub const UINT_FAST16_MAX: i32 = -1; +pub const UINT_FAST32_MAX: i32 = -1; +pub const INTPTR_MIN: i64 = -9223372036854775808; +pub const INTPTR_MAX: u64 = 9223372036854775807; +pub const UINTPTR_MAX: i32 = -1; +pub const PTRDIFF_MIN: i64 = -9223372036854775808; +pub const PTRDIFF_MAX: u64 = 9223372036854775807; +pub const SIG_ATOMIC_MIN: i32 = -2147483648; +pub const SIG_ATOMIC_MAX: u32 = 2147483647; +pub const SIZE_MAX: i32 = -1; +pub const WINT_MIN: u32 = 0; +pub const WINT_MAX: u32 = 4294967295; +pub const true_: u32 = 1; +pub const false_: u32 = 0; +pub const __bool_true_false_are_defined: u32 = 1; +pub const __GNUC_VA_LIST: u32 = 1; +pub const _STDLIB_H: u32 = 1; +pub const WNOHANG: u32 = 1; +pub const WUNTRACED: u32 = 2; +pub const WSTOPPED: u32 = 2; +pub const WEXITED: u32 = 4; +pub const WCONTINUED: u32 = 8; +pub const WNOWAIT: u32 = 16777216; +pub const __WNOTHREAD: u32 = 536870912; +pub const __WALL: u32 = 1073741824; +pub const __WCLONE: u32 = 2147483648; +pub const __ENUM_IDTYPE_T: u32 = 1; +pub const __W_CONTINUED: u32 = 65535; +pub const __WCOREFLAG: u32 = 128; +pub const __HAVE_FLOAT128: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT128: u32 = 0; +pub const __HAVE_FLOAT64X: u32 = 1; +pub const __HAVE_FLOAT64X_LONG_DOUBLE: u32 = 1; +pub const __HAVE_FLOAT16: u32 = 0; +pub const __HAVE_FLOAT32: u32 = 1; +pub const __HAVE_FLOAT64: u32 = 1; +pub const __HAVE_FLOAT32X: u32 = 1; +pub const __HAVE_FLOAT128X: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT16: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT32: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT64: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT32X: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT64X: u32 = 0; +pub const __HAVE_DISTINCT_FLOAT128X: u32 = 0; +pub const __HAVE_FLOATN_NOT_TYPEDEF: u32 = 0; +pub const __ldiv_t_defined: u32 = 1; +pub const __lldiv_t_defined: u32 = 1; +pub const RAND_MAX: u32 = 2147483647; +pub const EXIT_FAILURE: u32 = 1; +pub const EXIT_SUCCESS: u32 = 0; +pub const _SYS_TYPES_H: u32 = 1; +pub const __clock_t_defined: u32 = 1; +pub const __clockid_t_defined: u32 = 1; +pub const __time_t_defined: u32 = 1; +pub const __timer_t_defined: u32 = 1; +pub const __BIT_TYPES_DEFINED__: u32 = 1; +pub const _ENDIAN_H: u32 = 1; +pub const __LITTLE_ENDIAN: u32 = 1234; +pub const __BIG_ENDIAN: u32 = 4321; +pub const __PDP_ENDIAN: u32 = 3412; +pub const __BYTE_ORDER: u32 = 1234; +pub const __FLOAT_WORD_ORDER: u32 = 1234; +pub const LITTLE_ENDIAN: u32 = 1234; +pub const BIG_ENDIAN: u32 = 4321; +pub const PDP_ENDIAN: u32 = 3412; +pub const BYTE_ORDER: u32 = 1234; +pub const _BITS_BYTESWAP_H: u32 = 1; +pub const _BITS_UINTN_IDENTITY_H: u32 = 1; +pub const _SYS_SELECT_H: u32 = 1; +pub const __FD_ZERO_STOS: &'static [u8; 6usize] = b"stosq\0"; +pub const __sigset_t_defined: u32 = 1; +pub const __timeval_defined: u32 = 1; +pub const __timespec_defined: u32 = 1; +pub const FD_SETSIZE: u32 = 1024; +pub const _SYS_SYSMACROS_H: u32 = 1; +pub const _BITS_SYSMACROS_H: u32 = 1; +pub const _BITS_PTHREADTYPES_COMMON_H: u32 = 1; +pub const _THREAD_SHARED_TYPES_H: u32 = 1; +pub const _BITS_PTHREADTYPES_ARCH_H: u32 = 1; +pub const __SIZEOF_PTHREAD_MUTEX_T: u32 = 40; +pub const __SIZEOF_PTHREAD_ATTR_T: u32 = 56; +pub const __SIZEOF_PTHREAD_RWLOCK_T: u32 = 56; +pub const __SIZEOF_PTHREAD_BARRIER_T: u32 = 32; +pub const __SIZEOF_PTHREAD_MUTEXATTR_T: u32 = 4; +pub const __SIZEOF_PTHREAD_COND_T: u32 = 48; +pub const __SIZEOF_PTHREAD_CONDATTR_T: u32 = 4; +pub const __SIZEOF_PTHREAD_RWLOCKATTR_T: u32 = 8; +pub const __SIZEOF_PTHREAD_BARRIERATTR_T: u32 = 4; +pub const __PTHREAD_MUTEX_LOCK_ELISION: u32 = 1; +pub const __PTHREAD_MUTEX_NUSERS_AFTER_KIND: u32 = 0; +pub const __PTHREAD_MUTEX_USE_UNION: u32 = 0; +pub const __PTHREAD_RWLOCK_INT_FLAGS_SHARED: u32 = 1; +pub const __PTHREAD_MUTEX_HAVE_PREV: u32 = 1; +pub const __have_pthread_attr_t: u32 = 1; +pub const _ALLOCA_H: u32 = 1; +pub const SGX_FLAGS_INITTED: u32 = 1; +pub const SGX_FLAGS_DEBUG: u32 = 2; +pub const SGX_FLAGS_MODE64BIT: u32 = 4; +pub const SGX_FLAGS_PROVISION_KEY: u32 = 16; +pub const SGX_FLAGS_EINITTOKEN_KEY: u32 = 32; +pub const SGX_FLAGS_KSS: u32 = 128; +pub const SGX_XFRM_LEGACY: u32 = 3; +pub const SGX_XFRM_AVX: u32 = 6; +pub const SGX_XFRM_AVX512: u32 = 230; +pub const SGX_XFRM_MPX: u32 = 24; +pub const SGX_XFRM_RESERVED: i32 = -232; +pub const SGX_KEYSELECT_EINITTOKEN: u32 = 0; +pub const SGX_KEYSELECT_PROVISION: u32 = 1; +pub const SGX_KEYSELECT_PROVISION_SEAL: u32 = 2; +pub const SGX_KEYSELECT_REPORT: u32 = 3; +pub const SGX_KEYSELECT_SEAL: u32 = 4; +pub const SGX_KEYPOLICY_MRENCLAVE: u32 = 1; +pub const SGX_KEYPOLICY_MRSIGNER: u32 = 2; +pub const SGX_KEYPOLICY_NOISVPRODID: u32 = 4; +pub const SGX_KEYPOLICY_CONFIGID: u32 = 8; +pub const SGX_KEYPOLICY_ISVFAMILYID: u32 = 16; +pub const SGX_KEYPOLICY_ISVEXTPRODID: u32 = 32; +pub const SGX_KEYID_SIZE: u32 = 32; +pub const SGX_CPUSVN_SIZE: u32 = 16; +pub const SGX_CONFIGID_SIZE: u32 = 64; +pub const SGX_KEY_REQUEST_RESERVED2_BYTES: u32 = 434; +pub const SGX_HASH_SIZE: u32 = 32; +pub const SGX_MAC_SIZE: u32 = 16; +pub const SGX_REPORT_DATA_SIZE: u32 = 64; +pub const SGX_ISVEXT_PROD_ID_SIZE: u32 = 16; +pub const SGX_ISV_FAMILY_ID_SIZE: u32 = 16; +pub const SGX_TARGET_INFO_RESERVED1_BYTES: u32 = 2; +pub const SGX_TARGET_INFO_RESERVED2_BYTES: u32 = 8; +pub const SGX_TARGET_INFO_RESERVED3_BYTES: u32 = 384; +pub const MAX_EX_FEATURES_COUNT: u32 = 32; +pub const SGX_CREATE_ENCLAVE_EX_PCL_BIT_IDX: u32 = 0; +pub const SGX_CREATE_ENCLAVE_EX_PCL: u32 = 1; +pub const SGX_CREATE_ENCLAVE_EX_SWITCHLESS_BIT_IDX: u32 = 1; +pub const SGX_CREATE_ENCLAVE_EX_SWITCHLESS: u32 = 2; +pub const SGX_CREATE_ENCLAVE_EX_KSS_BIT_IDX: u32 = 2; +pub const SGX_CREATE_ENCLAVE_EX_KSS: u32 = 4; +pub const _SGX_LAST_EX_FEATURE_IDX_: u32 = 2; +pub const SGX_DEBUG_FLAG: u32 = 1; +pub const SGX_PLATFORM_INFO_SIZE: u32 = 101; +pub const SGXSD_AES_GCM_IV_SIZE: u32 = 12; +pub const SGXSD_AES_GCM_MAC_SIZE: u32 = 16; +pub const SGXSD_AES_GCM_KEY_SIZE: u32 = 32; +pub const SGXSD_CURVE25519_KEY_SIZE: u32 = 32; +pub const SGXSD_SHA256_HASH_SIZE: u32 = 32; +pub const _WCHAR_H: u32 = 1; +pub const __wint_t_defined: u32 = 1; +pub const _WINT_T: u32 = 1; +pub const __mbstate_t_defined: u32 = 1; +pub const ____mbstate_t_defined: u32 = 1; +pub const ____FILE_defined: u32 = 1; +pub const __FILE_defined: u32 = 1; +pub const _BITS_TYPES_LOCALE_T_H: u32 = 1; +pub const _BITS_TYPES___LOCALE_T_H: u32 = 1; +pub const WEOF: u32 = 4294967295; +pub const _STRING_H: u32 = 1; +pub const _STRINGS_H: u32 = 1; +pub const PS_CAP_TRUSTED_TIME: u32 = 1; +pub const PS_CAP_MONOTONIC_COUNTER: u32 = 2; +pub type __u_char = ::std::os::raw::c_uchar; +pub type __u_short = ::std::os::raw::c_ushort; +pub type __u_int = ::std::os::raw::c_uint; +pub type __u_long = ::std::os::raw::c_ulong; +pub type __int8_t = ::std::os::raw::c_schar; +pub type __uint8_t = ::std::os::raw::c_uchar; +pub type __int16_t = ::std::os::raw::c_short; +pub type __uint16_t = ::std::os::raw::c_ushort; +pub type __int32_t = ::std::os::raw::c_int; +pub type __uint32_t = ::std::os::raw::c_uint; +pub type __int64_t = ::std::os::raw::c_long; +pub type __uint64_t = ::std::os::raw::c_ulong; +pub type __quad_t = ::std::os::raw::c_long; +pub type __u_quad_t = ::std::os::raw::c_ulong; +pub type __intmax_t = ::std::os::raw::c_long; +pub type __uintmax_t = ::std::os::raw::c_ulong; +pub type __dev_t = ::std::os::raw::c_ulong; +pub type __uid_t = ::std::os::raw::c_uint; +pub type __gid_t = ::std::os::raw::c_uint; +pub type __ino_t = ::std::os::raw::c_ulong; +pub type __ino64_t = ::std::os::raw::c_ulong; +pub type __mode_t = ::std::os::raw::c_uint; +pub type __nlink_t = ::std::os::raw::c_ulong; +pub type __off_t = ::std::os::raw::c_long; +pub type __off64_t = ::std::os::raw::c_long; +pub type __pid_t = ::std::os::raw::c_int; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct __fsid_t { + pub __val: [::std::os::raw::c_int; 2usize], +} +#[test] +fn bindgen_test_layout___fsid_t() { + assert_eq!( + ::std::mem::size_of::<__fsid_t>(), + 8usize, + concat!("Size of: ", stringify!(__fsid_t)) + ); + assert_eq!( + ::std::mem::align_of::<__fsid_t>(), + 4usize, + concat!("Alignment of ", stringify!(__fsid_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__fsid_t>())).__val as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__fsid_t), + "::", + stringify!(__val) + ) + ); +} +pub type __clock_t = ::std::os::raw::c_long; +pub type __rlim_t = ::std::os::raw::c_ulong; +pub type __rlim64_t = ::std::os::raw::c_ulong; +pub type __id_t = ::std::os::raw::c_uint; +pub type __time_t = ::std::os::raw::c_long; +pub type __useconds_t = ::std::os::raw::c_uint; +pub type __suseconds_t = ::std::os::raw::c_long; +pub type __daddr_t = ::std::os::raw::c_int; +pub type __key_t = ::std::os::raw::c_int; +pub type __clockid_t = ::std::os::raw::c_int; +pub type __timer_t = *mut ::std::os::raw::c_void; +pub type __blksize_t = ::std::os::raw::c_long; +pub type __blkcnt_t = ::std::os::raw::c_long; +pub type __blkcnt64_t = ::std::os::raw::c_long; +pub type __fsblkcnt_t = ::std::os::raw::c_ulong; +pub type __fsblkcnt64_t = ::std::os::raw::c_ulong; +pub type __fsfilcnt_t = ::std::os::raw::c_ulong; +pub type __fsfilcnt64_t = ::std::os::raw::c_ulong; +pub type __fsword_t = ::std::os::raw::c_long; +pub type __ssize_t = ::std::os::raw::c_long; +pub type __syscall_slong_t = ::std::os::raw::c_long; +pub type __syscall_ulong_t = ::std::os::raw::c_ulong; +pub type __loff_t = __off64_t; +pub type __caddr_t = *mut ::std::os::raw::c_char; +pub type __intptr_t = ::std::os::raw::c_long; +pub type __socklen_t = ::std::os::raw::c_uint; +pub type __sig_atomic_t = ::std::os::raw::c_int; +pub type int_least8_t = ::std::os::raw::c_schar; +pub type int_least16_t = ::std::os::raw::c_short; +pub type int_least32_t = ::std::os::raw::c_int; +pub type int_least64_t = ::std::os::raw::c_long; +pub type uint_least8_t = ::std::os::raw::c_uchar; +pub type uint_least16_t = ::std::os::raw::c_ushort; +pub type uint_least32_t = ::std::os::raw::c_uint; +pub type uint_least64_t = ::std::os::raw::c_ulong; +pub type int_fast8_t = ::std::os::raw::c_schar; +pub type int_fast16_t = ::std::os::raw::c_long; +pub type int_fast32_t = ::std::os::raw::c_long; +pub type int_fast64_t = ::std::os::raw::c_long; +pub type uint_fast8_t = ::std::os::raw::c_uchar; +pub type uint_fast16_t = ::std::os::raw::c_ulong; +pub type uint_fast32_t = ::std::os::raw::c_ulong; +pub type uint_fast64_t = ::std::os::raw::c_ulong; +pub type intmax_t = __intmax_t; +pub type uintmax_t = __uintmax_t; +pub const KBUPD_REQUEST_TYPE_ANY: kbupd_request_type = 0; +pub const KBUPD_REQUEST_TYPE_BACKUP: kbupd_request_type = 1; +pub const KBUPD_REQUEST_TYPE_RESTORE: kbupd_request_type = 2; +pub const KBUPD_REQUEST_TYPE_DELETE: kbupd_request_type = 3; +pub type kbupd_request_type = u32; +pub use self::kbupd_request_type as kbupd_request_type_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_server_init_args {} +#[test] +fn bindgen_test_layout_sgxsd_server_init_args() { + assert_eq!( + ::std::mem::size_of::(), + 0usize, + concat!("Size of: ", stringify!(sgxsd_server_init_args)) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_server_init_args)) + ); +} +pub type sgxsd_server_init_args_t = sgxsd_server_init_args; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_server_handle_call_args { + pub backup_id: [u8; 32usize], + pub request_type: u32, +} +#[test] +fn bindgen_test_layout_sgxsd_server_handle_call_args() { + assert_eq!( + ::std::mem::size_of::(), + 36usize, + concat!("Size of: ", stringify!(sgxsd_server_handle_call_args)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(sgxsd_server_handle_call_args)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).backup_id as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_server_handle_call_args), + "::", + stringify!(backup_id) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).request_type as *const _ + as usize + }, + 32usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_server_handle_call_args), + "::", + stringify!(request_type) + ) + ); +} +pub type sgxsd_server_handle_call_args_t = sgxsd_server_handle_call_args; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_server_terminate_args {} +#[test] +fn bindgen_test_layout_sgxsd_server_terminate_args() { + assert_eq!( + ::std::mem::size_of::(), + 0usize, + concat!("Size of: ", stringify!(sgxsd_server_terminate_args)) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_server_terminate_args)) + ); +} +pub type sgxsd_server_terminate_args_t = sgxsd_server_terminate_args; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_ra_get_quote_args { + pub args: *const ::std::os::raw::c_void, +} +#[test] +fn bindgen_test_layout_sgxsd_ra_get_quote_args() { + assert_eq!( + ::std::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(sgxsd_ra_get_quote_args)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_ra_get_quote_args)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).args as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_ra_get_quote_args), + "::", + stringify!(args) + ) + ); +} +impl Default for sgxsd_ra_get_quote_args { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgxsd_ra_get_quote_args_t = sgxsd_ra_get_quote_args; +pub type va_list = __builtin_va_list; +pub type __gnuc_va_list = __builtin_va_list; +pub type wchar_t = ::std::os::raw::c_int; +pub const P_ALL: idtype_t = 0; +pub const P_PID: idtype_t = 1; +pub const P_PGID: idtype_t = 2; +pub type idtype_t = u32; +pub type _Float32 = f32; +pub type _Float64 = f64; +pub type _Float32x = f64; +pub type _Float64x = u128; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct div_t { + pub quot: ::std::os::raw::c_int, + pub rem: ::std::os::raw::c_int, +} +#[test] +fn bindgen_test_layout_div_t() { + assert_eq!( + ::std::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(div_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(div_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(div_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rem as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(div_t), + "::", + stringify!(rem) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct ldiv_t { + pub quot: ::std::os::raw::c_long, + pub rem: ::std::os::raw::c_long, +} +#[test] +fn bindgen_test_layout_ldiv_t() { + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(ldiv_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(ldiv_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(ldiv_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rem as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(ldiv_t), + "::", + stringify!(rem) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct lldiv_t { + pub quot: ::std::os::raw::c_longlong, + pub rem: ::std::os::raw::c_longlong, +} +#[test] +fn bindgen_test_layout_lldiv_t() { + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(lldiv_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(lldiv_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).quot as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(lldiv_t), + "::", + stringify!(quot) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rem as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(lldiv_t), + "::", + stringify!(rem) + ) + ); +} +extern "C" { + pub fn __ctype_get_mb_cur_max() -> usize; +} +extern "C" { + pub fn atof(__nptr: *const ::std::os::raw::c_char) -> f64; +} +extern "C" { + pub fn atoi(__nptr: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn atol(__nptr: *const ::std::os::raw::c_char) -> ::std::os::raw::c_long; +} +extern "C" { + pub fn atoll(__nptr: *const ::std::os::raw::c_char) -> ::std::os::raw::c_longlong; +} +extern "C" { + pub fn strtod( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + ) -> f64; +} +extern "C" { + pub fn strtof( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + ) -> f32; +} +extern "C" { + pub fn strtold( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + ) -> u128; +} +extern "C" { + pub fn strtol( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_long; +} +extern "C" { + pub fn strtoul( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_ulong; +} +extern "C" { + pub fn strtoq( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_longlong; +} +extern "C" { + pub fn strtouq( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_ulonglong; +} +extern "C" { + pub fn strtoll( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_longlong; +} +extern "C" { + pub fn strtoull( + __nptr: *const ::std::os::raw::c_char, + __endptr: *mut *mut ::std::os::raw::c_char, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_ulonglong; +} +extern "C" { + pub fn l64a(__n: ::std::os::raw::c_long) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn a64l(__s: *const ::std::os::raw::c_char) -> ::std::os::raw::c_long; +} +pub type u_char = __u_char; +pub type u_short = __u_short; +pub type u_int = __u_int; +pub type u_long = __u_long; +pub type quad_t = __quad_t; +pub type u_quad_t = __u_quad_t; +pub type fsid_t = __fsid_t; +pub type loff_t = __loff_t; +pub type ino_t = __ino_t; +pub type dev_t = __dev_t; +pub type gid_t = __gid_t; +pub type mode_t = __mode_t; +pub type nlink_t = __nlink_t; +pub type uid_t = __uid_t; +pub type off_t = __off_t; +pub type pid_t = __pid_t; +pub type id_t = __id_t; +pub type daddr_t = __daddr_t; +pub type caddr_t = __caddr_t; +pub type key_t = __key_t; +pub type clock_t = __clock_t; +pub type clockid_t = __clockid_t; +pub type time_t = __time_t; +pub type timer_t = __timer_t; +pub type ulong = ::std::os::raw::c_ulong; +pub type ushort = ::std::os::raw::c_ushort; +pub type uint = ::std::os::raw::c_uint; +pub type u_int8_t = ::std::os::raw::c_uchar; +pub type u_int16_t = ::std::os::raw::c_ushort; +pub type u_int32_t = ::std::os::raw::c_uint; +pub type u_int64_t = ::std::os::raw::c_ulong; +pub type register_t = ::std::os::raw::c_long; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct __sigset_t { + pub __val: [::std::os::raw::c_ulong; 16usize], +} +#[test] +fn bindgen_test_layout___sigset_t() { + assert_eq!( + ::std::mem::size_of::<__sigset_t>(), + 128usize, + concat!("Size of: ", stringify!(__sigset_t)) + ); + assert_eq!( + ::std::mem::align_of::<__sigset_t>(), + 8usize, + concat!("Alignment of ", stringify!(__sigset_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__sigset_t>())).__val as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__sigset_t), + "::", + stringify!(__val) + ) + ); +} +pub type sigset_t = __sigset_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct timeval { + pub tv_sec: __time_t, + pub tv_usec: __suseconds_t, +} +#[test] +fn bindgen_test_layout_timeval() { + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(timeval)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(timeval)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).tv_sec as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(timeval), + "::", + stringify!(tv_sec) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).tv_usec as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(timeval), + "::", + stringify!(tv_usec) + ) + ); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct timespec { + pub tv_sec: __time_t, + pub tv_nsec: __syscall_slong_t, +} +#[test] +fn bindgen_test_layout_timespec() { + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(timespec)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(timespec)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).tv_sec as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(timespec), + "::", + stringify!(tv_sec) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).tv_nsec as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(timespec), + "::", + stringify!(tv_nsec) + ) + ); +} +pub type suseconds_t = __suseconds_t; +pub type __fd_mask = ::std::os::raw::c_long; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct fd_set { + pub __fds_bits: [__fd_mask; 16usize], +} +#[test] +fn bindgen_test_layout_fd_set() { + assert_eq!( + ::std::mem::size_of::(), + 128usize, + concat!("Size of: ", stringify!(fd_set)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(fd_set)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__fds_bits as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(fd_set), + "::", + stringify!(__fds_bits) + ) + ); +} +pub type fd_mask = __fd_mask; +extern "C" { + pub fn select( + __nfds: ::std::os::raw::c_int, + __readfds: *mut fd_set, + __writefds: *mut fd_set, + __exceptfds: *mut fd_set, + __timeout: *mut timeval, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn pselect( + __nfds: ::std::os::raw::c_int, + __readfds: *mut fd_set, + __writefds: *mut fd_set, + __exceptfds: *mut fd_set, + __timeout: *const timespec, + __sigmask: *const __sigset_t, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn gnu_dev_major(__dev: __dev_t) -> ::std::os::raw::c_uint; +} +extern "C" { + pub fn gnu_dev_minor(__dev: __dev_t) -> ::std::os::raw::c_uint; +} +extern "C" { + pub fn gnu_dev_makedev( + __major: ::std::os::raw::c_uint, + __minor: ::std::os::raw::c_uint, + ) -> __dev_t; +} +pub type blksize_t = __blksize_t; +pub type blkcnt_t = __blkcnt_t; +pub type fsblkcnt_t = __fsblkcnt_t; +pub type fsfilcnt_t = __fsfilcnt_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct __pthread_rwlock_arch_t { + pub __readers: ::std::os::raw::c_uint, + pub __writers: ::std::os::raw::c_uint, + pub __wrphase_futex: ::std::os::raw::c_uint, + pub __writers_futex: ::std::os::raw::c_uint, + pub __pad3: ::std::os::raw::c_uint, + pub __pad4: ::std::os::raw::c_uint, + pub __cur_writer: ::std::os::raw::c_int, + pub __shared: ::std::os::raw::c_int, + pub __rwelision: ::std::os::raw::c_schar, + pub __pad1: [::std::os::raw::c_uchar; 7usize], + pub __pad2: ::std::os::raw::c_ulong, + pub __flags: ::std::os::raw::c_uint, +} +#[test] +fn bindgen_test_layout___pthread_rwlock_arch_t() { + assert_eq!( + ::std::mem::size_of::<__pthread_rwlock_arch_t>(), + 56usize, + concat!("Size of: ", stringify!(__pthread_rwlock_arch_t)) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_rwlock_arch_t>(), + 8usize, + concat!("Alignment of ", stringify!(__pthread_rwlock_arch_t)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__readers as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__readers) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__writers as *const _ as usize + }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__writers) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__wrphase_futex as *const _ as usize + }, + 8usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__wrphase_futex) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__writers_futex as *const _ as usize + }, + 12usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__writers_futex) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__pad3 as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__pad3) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__pad4 as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__pad4) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__cur_writer as *const _ as usize + }, + 24usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__cur_writer) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__shared as *const _ as usize + }, + 28usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__shared) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__rwelision as *const _ as usize + }, + 32usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__rwelision) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__pad1 as *const _ as usize }, + 33usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__pad1) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__pad2 as *const _ as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__pad2) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_rwlock_arch_t>())).__flags as *const _ as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(__pthread_rwlock_arch_t), + "::", + stringify!(__flags) + ) + ); +} +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct __pthread_internal_list { + pub __prev: *mut __pthread_internal_list, + pub __next: *mut __pthread_internal_list, +} +#[test] +fn bindgen_test_layout___pthread_internal_list() { + assert_eq!( + ::std::mem::size_of::<__pthread_internal_list>(), + 16usize, + concat!("Size of: ", stringify!(__pthread_internal_list)) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_internal_list>(), + 8usize, + concat!("Alignment of ", stringify!(__pthread_internal_list)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_internal_list>())).__prev as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_internal_list), + "::", + stringify!(__prev) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_internal_list>())).__next as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(__pthread_internal_list), + "::", + stringify!(__next) + ) + ); +} +impl Default for __pthread_internal_list { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type __pthread_list_t = __pthread_internal_list; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct __pthread_mutex_s { + pub __lock: ::std::os::raw::c_int, + pub __count: ::std::os::raw::c_uint, + pub __owner: ::std::os::raw::c_int, + pub __nusers: ::std::os::raw::c_uint, + pub __kind: ::std::os::raw::c_int, + pub __spins: ::std::os::raw::c_short, + pub __elision: ::std::os::raw::c_short, + pub __list: __pthread_list_t, +} +#[test] +fn bindgen_test_layout___pthread_mutex_s() { + assert_eq!( + ::std::mem::size_of::<__pthread_mutex_s>(), + 40usize, + concat!("Size of: ", stringify!(__pthread_mutex_s)) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_mutex_s>(), + 8usize, + concat!("Alignment of ", stringify!(__pthread_mutex_s)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__lock as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__lock) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__count as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__count) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__owner as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__owner) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__nusers as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__nusers) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__kind as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__kind) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__spins as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__spins) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__elision as *const _ as usize }, + 22usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__elision) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_mutex_s>())).__list as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(__pthread_mutex_s), + "::", + stringify!(__list) + ) + ); +} +impl Default for __pthread_mutex_s { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct __pthread_cond_s { + pub __bindgen_anon_1: __pthread_cond_s__bindgen_ty_1, + pub __bindgen_anon_2: __pthread_cond_s__bindgen_ty_2, + pub __g_refs: [::std::os::raw::c_uint; 2usize], + pub __g_size: [::std::os::raw::c_uint; 2usize], + pub __g1_orig_size: ::std::os::raw::c_uint, + pub __wrefs: ::std::os::raw::c_uint, + pub __g_signals: [::std::os::raw::c_uint; 2usize], +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union __pthread_cond_s__bindgen_ty_1 { + pub __wseq: ::std::os::raw::c_ulonglong, + pub __wseq32: __pthread_cond_s__bindgen_ty_1__bindgen_ty_1, + _bindgen_union_align: u64, +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct __pthread_cond_s__bindgen_ty_1__bindgen_ty_1 { + pub __low: ::std::os::raw::c_uint, + pub __high: ::std::os::raw::c_uint, +} +#[test] +fn bindgen_test_layout___pthread_cond_s__bindgen_ty_1__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::<__pthread_cond_s__bindgen_ty_1__bindgen_ty_1>(), + 8usize, + concat!( + "Size of: ", + stringify!(__pthread_cond_s__bindgen_ty_1__bindgen_ty_1) + ) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_cond_s__bindgen_ty_1__bindgen_ty_1>(), + 4usize, + concat!( + "Alignment of ", + stringify!(__pthread_cond_s__bindgen_ty_1__bindgen_ty_1) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_1__bindgen_ty_1>())).__low + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_1__bindgen_ty_1), + "::", + stringify!(__low) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_1__bindgen_ty_1>())).__high + as *const _ as usize + }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_1__bindgen_ty_1), + "::", + stringify!(__high) + ) + ); +} +#[test] +fn bindgen_test_layout___pthread_cond_s__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::<__pthread_cond_s__bindgen_ty_1>(), + 8usize, + concat!("Size of: ", stringify!(__pthread_cond_s__bindgen_ty_1)) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_cond_s__bindgen_ty_1>(), + 8usize, + concat!("Alignment of ", stringify!(__pthread_cond_s__bindgen_ty_1)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_1>())).__wseq as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_1), + "::", + stringify!(__wseq) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_1>())).__wseq32 as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_1), + "::", + stringify!(__wseq32) + ) + ); +} +impl Default for __pthread_cond_s__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union __pthread_cond_s__bindgen_ty_2 { + pub __g1_start: ::std::os::raw::c_ulonglong, + pub __g1_start32: __pthread_cond_s__bindgen_ty_2__bindgen_ty_1, + _bindgen_union_align: u64, +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct __pthread_cond_s__bindgen_ty_2__bindgen_ty_1 { + pub __low: ::std::os::raw::c_uint, + pub __high: ::std::os::raw::c_uint, +} +#[test] +fn bindgen_test_layout___pthread_cond_s__bindgen_ty_2__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::<__pthread_cond_s__bindgen_ty_2__bindgen_ty_1>(), + 8usize, + concat!( + "Size of: ", + stringify!(__pthread_cond_s__bindgen_ty_2__bindgen_ty_1) + ) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_cond_s__bindgen_ty_2__bindgen_ty_1>(), + 4usize, + concat!( + "Alignment of ", + stringify!(__pthread_cond_s__bindgen_ty_2__bindgen_ty_1) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_2__bindgen_ty_1>())).__low + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_2__bindgen_ty_1), + "::", + stringify!(__low) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_2__bindgen_ty_1>())).__high + as *const _ as usize + }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_2__bindgen_ty_1), + "::", + stringify!(__high) + ) + ); +} +#[test] +fn bindgen_test_layout___pthread_cond_s__bindgen_ty_2() { + assert_eq!( + ::std::mem::size_of::<__pthread_cond_s__bindgen_ty_2>(), + 8usize, + concat!("Size of: ", stringify!(__pthread_cond_s__bindgen_ty_2)) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_cond_s__bindgen_ty_2>(), + 8usize, + concat!("Alignment of ", stringify!(__pthread_cond_s__bindgen_ty_2)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_2>())).__g1_start as *const _ + as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_2), + "::", + stringify!(__g1_start) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__pthread_cond_s__bindgen_ty_2>())).__g1_start32 as *const _ + as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s__bindgen_ty_2), + "::", + stringify!(__g1_start32) + ) + ); +} +impl Default for __pthread_cond_s__bindgen_ty_2 { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout___pthread_cond_s() { + assert_eq!( + ::std::mem::size_of::<__pthread_cond_s>(), + 48usize, + concat!("Size of: ", stringify!(__pthread_cond_s)) + ); + assert_eq!( + ::std::mem::align_of::<__pthread_cond_s>(), + 8usize, + concat!("Alignment of ", stringify!(__pthread_cond_s)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_cond_s>())).__g_refs as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s), + "::", + stringify!(__g_refs) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_cond_s>())).__g_size as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s), + "::", + stringify!(__g_size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_cond_s>())).__g1_orig_size as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s), + "::", + stringify!(__g1_orig_size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_cond_s>())).__wrefs as *const _ as usize }, + 36usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s), + "::", + stringify!(__wrefs) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__pthread_cond_s>())).__g_signals as *const _ as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(__pthread_cond_s), + "::", + stringify!(__g_signals) + ) + ); +} +impl Default for __pthread_cond_s { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type pthread_t = ::std::os::raw::c_ulong; +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_mutexattr_t { + pub __size: [::std::os::raw::c_char; 4usize], + pub __align: ::std::os::raw::c_int, + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout_pthread_mutexattr_t() { + assert_eq!( + ::std::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(pthread_mutexattr_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(pthread_mutexattr_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_mutexattr_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_mutexattr_t), + "::", + stringify!(__align) + ) + ); +} +impl Default for pthread_mutexattr_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_condattr_t { + pub __size: [::std::os::raw::c_char; 4usize], + pub __align: ::std::os::raw::c_int, + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout_pthread_condattr_t() { + assert_eq!( + ::std::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(pthread_condattr_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(pthread_condattr_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_condattr_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_condattr_t), + "::", + stringify!(__align) + ) + ); +} +impl Default for pthread_condattr_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type pthread_key_t = ::std::os::raw::c_uint; +pub type pthread_once_t = ::std::os::raw::c_int; +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_attr_t { + pub __size: [::std::os::raw::c_char; 56usize], + pub __align: ::std::os::raw::c_long, + _bindgen_union_align: [u64; 7usize], +} +#[test] +fn bindgen_test_layout_pthread_attr_t() { + assert_eq!( + ::std::mem::size_of::(), + 56usize, + concat!("Size of: ", stringify!(pthread_attr_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(pthread_attr_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_attr_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_attr_t), + "::", + stringify!(__align) + ) + ); +} +impl Default for pthread_attr_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_mutex_t { + pub __data: __pthread_mutex_s, + pub __size: [::std::os::raw::c_char; 40usize], + pub __align: ::std::os::raw::c_long, + _bindgen_union_align: [u64; 5usize], +} +#[test] +fn bindgen_test_layout_pthread_mutex_t() { + assert_eq!( + ::std::mem::size_of::(), + 40usize, + concat!("Size of: ", stringify!(pthread_mutex_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(pthread_mutex_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_mutex_t), + "::", + stringify!(__data) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_mutex_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_mutex_t), + "::", + stringify!(__align) + ) + ); +} +impl Default for pthread_mutex_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_cond_t { + pub __data: __pthread_cond_s, + pub __size: [::std::os::raw::c_char; 48usize], + pub __align: ::std::os::raw::c_longlong, + _bindgen_union_align: [u64; 6usize], +} +#[test] +fn bindgen_test_layout_pthread_cond_t() { + assert_eq!( + ::std::mem::size_of::(), + 48usize, + concat!("Size of: ", stringify!(pthread_cond_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(pthread_cond_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_cond_t), + "::", + stringify!(__data) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_cond_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_cond_t), + "::", + stringify!(__align) + ) + ); +} +impl Default for pthread_cond_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_rwlock_t { + pub __data: __pthread_rwlock_arch_t, + pub __size: [::std::os::raw::c_char; 56usize], + pub __align: ::std::os::raw::c_long, + _bindgen_union_align: [u64; 7usize], +} +#[test] +fn bindgen_test_layout_pthread_rwlock_t() { + assert_eq!( + ::std::mem::size_of::(), + 56usize, + concat!("Size of: ", stringify!(pthread_rwlock_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(pthread_rwlock_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_rwlock_t), + "::", + stringify!(__data) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_rwlock_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_rwlock_t), + "::", + stringify!(__align) + ) + ); +} +impl Default for pthread_rwlock_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_rwlockattr_t { + pub __size: [::std::os::raw::c_char; 8usize], + pub __align: ::std::os::raw::c_long, + _bindgen_union_align: u64, +} +#[test] +fn bindgen_test_layout_pthread_rwlockattr_t() { + assert_eq!( + ::std::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(pthread_rwlockattr_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(pthread_rwlockattr_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_rwlockattr_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_rwlockattr_t), + "::", + stringify!(__align) + ) + ); +} +impl Default for pthread_rwlockattr_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type pthread_spinlock_t = ::std::os::raw::c_int; +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_barrier_t { + pub __size: [::std::os::raw::c_char; 32usize], + pub __align: ::std::os::raw::c_long, + _bindgen_union_align: [u64; 4usize], +} +#[test] +fn bindgen_test_layout_pthread_barrier_t() { + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(pthread_barrier_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(pthread_barrier_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_barrier_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_barrier_t), + "::", + stringify!(__align) + ) + ); +} +impl Default for pthread_barrier_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union pthread_barrierattr_t { + pub __size: [::std::os::raw::c_char; 4usize], + pub __align: ::std::os::raw::c_int, + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout_pthread_barrierattr_t() { + assert_eq!( + ::std::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(pthread_barrierattr_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(pthread_barrierattr_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__size as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_barrierattr_t), + "::", + stringify!(__size) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__align as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(pthread_barrierattr_t), + "::", + stringify!(__align) + ) + ); +} +impl Default for pthread_barrierattr_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +extern "C" { + pub fn random() -> ::std::os::raw::c_long; +} +extern "C" { + pub fn srandom(__seed: ::std::os::raw::c_uint); +} +extern "C" { + pub fn initstate( + __seed: ::std::os::raw::c_uint, + __statebuf: *mut ::std::os::raw::c_char, + __statelen: usize, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn setstate(__statebuf: *mut ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char; +} +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct random_data { + pub fptr: *mut i32, + pub rptr: *mut i32, + pub state: *mut i32, + pub rand_type: ::std::os::raw::c_int, + pub rand_deg: ::std::os::raw::c_int, + pub rand_sep: ::std::os::raw::c_int, + pub end_ptr: *mut i32, +} +#[test] +fn bindgen_test_layout_random_data() { + assert_eq!( + ::std::mem::size_of::(), + 48usize, + concat!("Size of: ", stringify!(random_data)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(random_data)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).fptr as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(fptr) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rptr as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(rptr) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).state as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(state) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rand_type as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(rand_type) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rand_deg as *const _ as usize }, + 28usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(rand_deg) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).rand_sep as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(rand_sep) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).end_ptr as *const _ as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(random_data), + "::", + stringify!(end_ptr) + ) + ); +} +impl Default for random_data { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +extern "C" { + pub fn random_r(__buf: *mut random_data, __result: *mut i32) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn srandom_r( + __seed: ::std::os::raw::c_uint, + __buf: *mut random_data, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn initstate_r( + __seed: ::std::os::raw::c_uint, + __statebuf: *mut ::std::os::raw::c_char, + __statelen: usize, + __buf: *mut random_data, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn setstate_r( + __statebuf: *mut ::std::os::raw::c_char, + __buf: *mut random_data, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn rand() -> ::std::os::raw::c_int; +} +extern "C" { + pub fn srand(__seed: ::std::os::raw::c_uint); +} +extern "C" { + pub fn rand_r(__seed: *mut ::std::os::raw::c_uint) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn drand48() -> f64; +} +extern "C" { + pub fn erand48(__xsubi: *mut ::std::os::raw::c_ushort) -> f64; +} +extern "C" { + pub fn lrand48() -> ::std::os::raw::c_long; +} +extern "C" { + pub fn nrand48(__xsubi: *mut ::std::os::raw::c_ushort) -> ::std::os::raw::c_long; +} +extern "C" { + pub fn mrand48() -> ::std::os::raw::c_long; +} +extern "C" { + pub fn jrand48(__xsubi: *mut ::std::os::raw::c_ushort) -> ::std::os::raw::c_long; +} +extern "C" { + pub fn srand48(__seedval: ::std::os::raw::c_long); +} +extern "C" { + pub fn seed48(__seed16v: *mut ::std::os::raw::c_ushort) -> *mut ::std::os::raw::c_ushort; +} +extern "C" { + pub fn lcong48(__param: *mut ::std::os::raw::c_ushort); +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct drand48_data { + pub __x: [::std::os::raw::c_ushort; 3usize], + pub __old_x: [::std::os::raw::c_ushort; 3usize], + pub __c: ::std::os::raw::c_ushort, + pub __init: ::std::os::raw::c_ushort, + pub __a: ::std::os::raw::c_ulonglong, +} +#[test] +fn bindgen_test_layout_drand48_data() { + assert_eq!( + ::std::mem::size_of::(), + 24usize, + concat!("Size of: ", stringify!(drand48_data)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(drand48_data)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__x as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(drand48_data), + "::", + stringify!(__x) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__old_x as *const _ as usize }, + 6usize, + concat!( + "Offset of field: ", + stringify!(drand48_data), + "::", + stringify!(__old_x) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__c as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(drand48_data), + "::", + stringify!(__c) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__init as *const _ as usize }, + 14usize, + concat!( + "Offset of field: ", + stringify!(drand48_data), + "::", + stringify!(__init) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).__a as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(drand48_data), + "::", + stringify!(__a) + ) + ); +} +extern "C" { + pub fn drand48_r(__buffer: *mut drand48_data, __result: *mut f64) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn erand48_r( + __xsubi: *mut ::std::os::raw::c_ushort, + __buffer: *mut drand48_data, + __result: *mut f64, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn lrand48_r( + __buffer: *mut drand48_data, + __result: *mut ::std::os::raw::c_long, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn nrand48_r( + __xsubi: *mut ::std::os::raw::c_ushort, + __buffer: *mut drand48_data, + __result: *mut ::std::os::raw::c_long, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mrand48_r( + __buffer: *mut drand48_data, + __result: *mut ::std::os::raw::c_long, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn jrand48_r( + __xsubi: *mut ::std::os::raw::c_ushort, + __buffer: *mut drand48_data, + __result: *mut ::std::os::raw::c_long, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn srand48_r( + __seedval: ::std::os::raw::c_long, + __buffer: *mut drand48_data, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn seed48_r( + __seed16v: *mut ::std::os::raw::c_ushort, + __buffer: *mut drand48_data, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn lcong48_r( + __param: *mut ::std::os::raw::c_ushort, + __buffer: *mut drand48_data, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn malloc(__size: ::std::os::raw::c_ulong) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn calloc( + __nmemb: ::std::os::raw::c_ulong, + __size: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn realloc( + __ptr: *mut ::std::os::raw::c_void, + __size: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn free(__ptr: *mut ::std::os::raw::c_void); +} +extern "C" { + pub fn alloca(__size: ::std::os::raw::c_ulong) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn valloc(__size: usize) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn posix_memalign( + __memptr: *mut *mut ::std::os::raw::c_void, + __alignment: usize, + __size: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn aligned_alloc(__alignment: usize, __size: usize) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn abort(); +} +extern "C" { + pub fn atexit(__func: ::std::option::Option) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn at_quick_exit( + __func: ::std::option::Option, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn on_exit( + __func: ::std::option::Option< + unsafe extern "C" fn( + __status: ::std::os::raw::c_int, + __arg: *mut ::std::os::raw::c_void, + ), + >, + __arg: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn exit(__status: ::std::os::raw::c_int); +} +extern "C" { + pub fn quick_exit(__status: ::std::os::raw::c_int); +} +extern "C" { + pub fn _Exit(__status: ::std::os::raw::c_int); +} +extern "C" { + pub fn getenv(__name: *const ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn putenv(__string: *mut ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn setenv( + __name: *const ::std::os::raw::c_char, + __value: *const ::std::os::raw::c_char, + __replace: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn unsetenv(__name: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn clearenv() -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mktemp(__template: *mut ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn mkstemp(__template: *mut ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mkstemps( + __template: *mut ::std::os::raw::c_char, + __suffixlen: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mkdtemp(__template: *mut ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn system(__command: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn realpath( + __name: *const ::std::os::raw::c_char, + __resolved: *mut ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +pub type __compar_fn_t = ::std::option::Option< + unsafe extern "C" fn( + arg1: *const ::std::os::raw::c_void, + arg2: *const ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int, +>; +extern "C" { + pub fn bsearch( + __key: *const ::std::os::raw::c_void, + __base: *const ::std::os::raw::c_void, + __nmemb: usize, + __size: usize, + __compar: __compar_fn_t, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn qsort( + __base: *mut ::std::os::raw::c_void, + __nmemb: usize, + __size: usize, + __compar: __compar_fn_t, + ); +} +extern "C" { + pub fn abs(__x: ::std::os::raw::c_int) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn labs(__x: ::std::os::raw::c_long) -> ::std::os::raw::c_long; +} +extern "C" { + pub fn llabs(__x: ::std::os::raw::c_longlong) -> ::std::os::raw::c_longlong; +} +extern "C" { + pub fn div(__numer: ::std::os::raw::c_int, __denom: ::std::os::raw::c_int) -> div_t; +} +extern "C" { + pub fn ldiv(__numer: ::std::os::raw::c_long, __denom: ::std::os::raw::c_long) -> ldiv_t; +} +extern "C" { + pub fn lldiv( + __numer: ::std::os::raw::c_longlong, + __denom: ::std::os::raw::c_longlong, + ) -> lldiv_t; +} +extern "C" { + pub fn ecvt( + __value: f64, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn fcvt( + __value: f64, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn gcvt( + __value: f64, + __ndigit: ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn qecvt( + __value: u128, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn qfcvt( + __value: u128, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn qgcvt( + __value: u128, + __ndigit: ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn ecvt_r( + __value: f64, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + __len: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn fcvt_r( + __value: f64, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + __len: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn qecvt_r( + __value: u128, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + __len: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn qfcvt_r( + __value: u128, + __ndigit: ::std::os::raw::c_int, + __decpt: *mut ::std::os::raw::c_int, + __sign: *mut ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + __len: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mblen(__s: *const ::std::os::raw::c_char, __n: usize) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mbtowc( + __pwc: *mut wchar_t, + __s: *const ::std::os::raw::c_char, + __n: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wctomb(__s: *mut ::std::os::raw::c_char, __wchar: wchar_t) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mbstowcs(__pwcs: *mut wchar_t, __s: *const ::std::os::raw::c_char, __n: usize) -> usize; +} +extern "C" { + pub fn wcstombs(__s: *mut ::std::os::raw::c_char, __pwcs: *const wchar_t, __n: usize) -> usize; +} +extern "C" { + pub fn rpmatch(__response: *const ::std::os::raw::c_char) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn getsubopt( + __optionp: *mut *mut ::std::os::raw::c_char, + __tokens: *const *mut ::std::os::raw::c_char, + __valuep: *mut *mut ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn getloadavg(__loadavg: *mut f64, __nelem: ::std::os::raw::c_int) + -> ::std::os::raw::c_int; +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _attributes_t { + pub flags: u64, + pub xfrm: u64, +} +#[test] +fn bindgen_test_layout__attributes_t() { + assert_eq!( + ::std::mem::size_of::<_attributes_t>(), + 16usize, + concat!("Size of: ", stringify!(_attributes_t)) + ); + assert_eq!( + ::std::mem::align_of::<_attributes_t>(), + 8usize, + concat!("Alignment of ", stringify!(_attributes_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_attributes_t>())).flags as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_attributes_t), + "::", + stringify!(flags) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_attributes_t>())).xfrm as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_attributes_t), + "::", + stringify!(xfrm) + ) + ); +} +pub type sgx_attributes_t = _attributes_t; +pub type sgx_misc_select_t = u32; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_misc_attribute_t { + pub secs_attr: sgx_attributes_t, + pub misc_select: sgx_misc_select_t, +} +#[test] +fn bindgen_test_layout__sgx_misc_attribute_t() { + assert_eq!( + ::std::mem::size_of::<_sgx_misc_attribute_t>(), + 24usize, + concat!("Size of: ", stringify!(_sgx_misc_attribute_t)) + ); + assert_eq!( + ::std::mem::align_of::<_sgx_misc_attribute_t>(), + 8usize, + concat!("Alignment of ", stringify!(_sgx_misc_attribute_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_sgx_misc_attribute_t>())).secs_attr as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_misc_attribute_t), + "::", + stringify!(secs_attr) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<_sgx_misc_attribute_t>())).misc_select as *const _ as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_sgx_misc_attribute_t), + "::", + stringify!(misc_select) + ) + ); +} +pub type sgx_misc_attribute_t = _sgx_misc_attribute_t; +pub const SGX_SUCCESS: _status_t = 0; +pub const SGX_ERROR_UNEXPECTED: _status_t = 1; +pub const SGX_ERROR_INVALID_PARAMETER: _status_t = 2; +pub const SGX_ERROR_OUT_OF_MEMORY: _status_t = 3; +pub const SGX_ERROR_ENCLAVE_LOST: _status_t = 4; +pub const SGX_ERROR_INVALID_STATE: _status_t = 5; +pub const SGX_ERROR_FEATURE_NOT_SUPPORTED: _status_t = 8; +pub const SGX_ERROR_INVALID_FUNCTION: _status_t = 4097; +pub const SGX_ERROR_OUT_OF_TCS: _status_t = 4099; +pub const SGX_ERROR_ENCLAVE_CRASHED: _status_t = 4102; +pub const SGX_ERROR_ECALL_NOT_ALLOWED: _status_t = 4103; +pub const SGX_ERROR_OCALL_NOT_ALLOWED: _status_t = 4104; +pub const SGX_ERROR_STACK_OVERRUN: _status_t = 4105; +pub const SGX_ERROR_UNDEFINED_SYMBOL: _status_t = 8192; +pub const SGX_ERROR_INVALID_ENCLAVE: _status_t = 8193; +pub const SGX_ERROR_INVALID_ENCLAVE_ID: _status_t = 8194; +pub const SGX_ERROR_INVALID_SIGNATURE: _status_t = 8195; +pub const SGX_ERROR_NDEBUG_ENCLAVE: _status_t = 8196; +pub const SGX_ERROR_OUT_OF_EPC: _status_t = 8197; +pub const SGX_ERROR_NO_DEVICE: _status_t = 8198; +pub const SGX_ERROR_MEMORY_MAP_CONFLICT: _status_t = 8199; +pub const SGX_ERROR_INVALID_METADATA: _status_t = 8201; +pub const SGX_ERROR_DEVICE_BUSY: _status_t = 8204; +pub const SGX_ERROR_INVALID_VERSION: _status_t = 8205; +pub const SGX_ERROR_MODE_INCOMPATIBLE: _status_t = 8206; +pub const SGX_ERROR_ENCLAVE_FILE_ACCESS: _status_t = 8207; +pub const SGX_ERROR_INVALID_MISC: _status_t = 8208; +pub const SGX_ERROR_INVALID_LAUNCH_TOKEN: _status_t = 8209; +pub const SGX_ERROR_MAC_MISMATCH: _status_t = 12289; +pub const SGX_ERROR_INVALID_ATTRIBUTE: _status_t = 12290; +pub const SGX_ERROR_INVALID_CPUSVN: _status_t = 12291; +pub const SGX_ERROR_INVALID_ISVSVN: _status_t = 12292; +pub const SGX_ERROR_INVALID_KEYNAME: _status_t = 12293; +pub const SGX_ERROR_SERVICE_UNAVAILABLE: _status_t = 16385; +pub const SGX_ERROR_SERVICE_TIMEOUT: _status_t = 16386; +pub const SGX_ERROR_AE_INVALID_EPIDBLOB: _status_t = 16387; +pub const SGX_ERROR_SERVICE_INVALID_PRIVILEGE: _status_t = 16388; +pub const SGX_ERROR_EPID_MEMBER_REVOKED: _status_t = 16389; +pub const SGX_ERROR_UPDATE_NEEDED: _status_t = 16390; +pub const SGX_ERROR_NETWORK_FAILURE: _status_t = 16391; +pub const SGX_ERROR_AE_SESSION_INVALID: _status_t = 16392; +pub const SGX_ERROR_BUSY: _status_t = 16394; +pub const SGX_ERROR_MC_NOT_FOUND: _status_t = 16396; +pub const SGX_ERROR_MC_NO_ACCESS_RIGHT: _status_t = 16397; +pub const SGX_ERROR_MC_USED_UP: _status_t = 16398; +pub const SGX_ERROR_MC_OVER_QUOTA: _status_t = 16399; +pub const SGX_ERROR_KDF_MISMATCH: _status_t = 16401; +pub const SGX_ERROR_UNRECOGNIZED_PLATFORM: _status_t = 16402; +pub const SGX_ERROR_NO_PRIVILEGE: _status_t = 20482; +pub const SGX_ERROR_PCL_ENCRYPTED: _status_t = 24577; +pub const SGX_ERROR_PCL_NOT_ENCRYPTED: _status_t = 24578; +pub const SGX_ERROR_PCL_MAC_MISMATCH: _status_t = 24579; +pub const SGX_ERROR_PCL_SHA_MISMATCH: _status_t = 24580; +pub const SGX_ERROR_PCL_GUID_MISMATCH: _status_t = 24581; +pub const SGX_ERROR_FILE_BAD_STATUS: _status_t = 28673; +pub const SGX_ERROR_FILE_NO_KEY_ID: _status_t = 28674; +pub const SGX_ERROR_FILE_NAME_MISMATCH: _status_t = 28675; +pub const SGX_ERROR_FILE_NOT_SGX_FILE: _status_t = 28676; +pub const SGX_ERROR_FILE_CANT_OPEN_RECOVERY_FILE: _status_t = 28677; +pub const SGX_ERROR_FILE_CANT_WRITE_RECOVERY_FILE: _status_t = 28678; +pub const SGX_ERROR_FILE_RECOVERY_NEEDED: _status_t = 28679; +pub const SGX_ERROR_FILE_FLUSH_FAILED: _status_t = 28680; +pub const SGX_ERROR_FILE_CLOSE_FAILED: _status_t = 28681; +pub const SGX_ERROR_UNSUPPORTED_ATT_KEY_ID: _status_t = 32769; +pub const SGX_ERROR_ATT_KEY_CERTIFICATION_FAILURE: _status_t = 32770; +pub const SGX_ERROR_ATT_KEY_UNINITIALIZED: _status_t = 32771; +pub const SGX_ERROR_INVALID_ATT_KEY_CERT_DATA: _status_t = 32772; +pub const SGX_INTERNAL_ERROR_ENCLAVE_CREATE_INTERRUPTED: _status_t = 61441; +pub type _status_t = u32; +pub use self::_status_t as sgx_status_t; +pub type sgx_enclave_id_t = u64; +pub type sgx_key_128bit_t = [u8; 16usize]; +pub type sgx_isv_svn_t = u16; +pub type sgx_config_svn_t = u16; +pub type sgx_config_id_t = [u8; 64usize]; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_cpu_svn_t { + pub svn: [u8; 16usize], +} +#[test] +fn bindgen_test_layout__sgx_cpu_svn_t() { + assert_eq!( + ::std::mem::size_of::<_sgx_cpu_svn_t>(), + 16usize, + concat!("Size of: ", stringify!(_sgx_cpu_svn_t)) + ); + assert_eq!( + ::std::mem::align_of::<_sgx_cpu_svn_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_cpu_svn_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_sgx_cpu_svn_t>())).svn as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_cpu_svn_t), + "::", + stringify!(svn) + ) + ); +} +pub type sgx_cpu_svn_t = _sgx_cpu_svn_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_key_id_t { + pub id: [u8; 32usize], +} +#[test] +fn bindgen_test_layout__sgx_key_id_t() { + assert_eq!( + ::std::mem::size_of::<_sgx_key_id_t>(), + 32usize, + concat!("Size of: ", stringify!(_sgx_key_id_t)) + ); + assert_eq!( + ::std::mem::align_of::<_sgx_key_id_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_key_id_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_sgx_key_id_t>())).id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_key_id_t), + "::", + stringify!(id) + ) + ); +} +pub type sgx_key_id_t = _sgx_key_id_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _key_request_t { + pub key_name: u16, + pub key_policy: u16, + pub isv_svn: sgx_isv_svn_t, + pub reserved1: u16, + pub cpu_svn: sgx_cpu_svn_t, + pub attribute_mask: sgx_attributes_t, + pub key_id: sgx_key_id_t, + pub misc_mask: sgx_misc_select_t, + pub config_svn: sgx_config_svn_t, + pub reserved2: [u8; 434usize], +} +#[test] +fn bindgen_test_layout__key_request_t() { + assert_eq!( + ::std::mem::size_of::<_key_request_t>(), + 512usize, + concat!("Size of: ", stringify!(_key_request_t)) + ); + assert_eq!( + ::std::mem::align_of::<_key_request_t>(), + 8usize, + concat!("Alignment of ", stringify!(_key_request_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_key_request_t>())).key_name as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(key_name) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_key_request_t>())).key_policy as *const _ as usize }, + 2usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(key_policy) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_key_request_t>())).isv_svn as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(isv_svn) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_key_request_t>())).reserved1 as *const _ as usize }, + 6usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(reserved1) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_key_request_t>())).cpu_svn as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(cpu_svn) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_key_request_t>())).attribute_mask as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(attribute_mask) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_key_request_t>())).key_id as *const _ as usize }, + 40usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(key_id) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_key_request_t>())).misc_mask as *const _ as usize }, + 72usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(misc_mask) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_key_request_t>())).config_svn as *const _ as usize }, + 76usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(config_svn) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_key_request_t>())).reserved2 as *const _ as usize }, + 78usize, + concat!( + "Offset of field: ", + stringify!(_key_request_t), + "::", + stringify!(reserved2) + ) + ); +} +impl Default for _key_request_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgx_key_request_t = _key_request_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_measurement_t { + pub m: [u8; 32usize], +} +#[test] +fn bindgen_test_layout__sgx_measurement_t() { + assert_eq!( + ::std::mem::size_of::<_sgx_measurement_t>(), + 32usize, + concat!("Size of: ", stringify!(_sgx_measurement_t)) + ); + assert_eq!( + ::std::mem::align_of::<_sgx_measurement_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_measurement_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_sgx_measurement_t>())).m as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_measurement_t), + "::", + stringify!(m) + ) + ); +} +pub type sgx_measurement_t = _sgx_measurement_t; +pub type sgx_mac_t = [u8; 16usize]; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _sgx_report_data_t { + pub d: [u8; 64usize], +} +#[test] +fn bindgen_test_layout__sgx_report_data_t() { + assert_eq!( + ::std::mem::size_of::<_sgx_report_data_t>(), + 64usize, + concat!("Size of: ", stringify!(_sgx_report_data_t)) + ); + assert_eq!( + ::std::mem::align_of::<_sgx_report_data_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_report_data_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_sgx_report_data_t>())).d as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_report_data_t), + "::", + stringify!(d) + ) + ); +} +impl Default for _sgx_report_data_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgx_report_data_t = _sgx_report_data_t; +pub type sgx_prod_id_t = u16; +pub type sgx_isvext_prod_id_t = [u8; 16usize]; +pub type sgx_isvfamily_id_t = [u8; 16usize]; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _target_info_t { + pub mr_enclave: sgx_measurement_t, + pub attributes: sgx_attributes_t, + pub reserved1: [u8; 2usize], + pub config_svn: sgx_config_svn_t, + pub misc_select: sgx_misc_select_t, + pub reserved2: [u8; 8usize], + pub config_id: sgx_config_id_t, + pub reserved3: [u8; 384usize], +} +#[test] +fn bindgen_test_layout__target_info_t() { + assert_eq!( + ::std::mem::size_of::<_target_info_t>(), + 512usize, + concat!("Size of: ", stringify!(_target_info_t)) + ); + assert_eq!( + ::std::mem::align_of::<_target_info_t>(), + 8usize, + concat!("Alignment of ", stringify!(_target_info_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_target_info_t>())).mr_enclave as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(mr_enclave) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_target_info_t>())).attributes as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(attributes) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_target_info_t>())).reserved1 as *const _ as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(reserved1) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_target_info_t>())).config_svn as *const _ as usize }, + 50usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(config_svn) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_target_info_t>())).misc_select as *const _ as usize }, + 52usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(misc_select) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_target_info_t>())).reserved2 as *const _ as usize }, + 56usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(reserved2) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_target_info_t>())).config_id as *const _ as usize }, + 64usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(config_id) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_target_info_t>())).reserved3 as *const _ as usize }, + 128usize, + concat!( + "Offset of field: ", + stringify!(_target_info_t), + "::", + stringify!(reserved3) + ) + ); +} +impl Default for _target_info_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgx_target_info_t = _target_info_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _report_body_t { + pub cpu_svn: sgx_cpu_svn_t, + pub misc_select: sgx_misc_select_t, + pub reserved1: [u8; 12usize], + pub isv_ext_prod_id: sgx_isvext_prod_id_t, + pub attributes: sgx_attributes_t, + pub mr_enclave: sgx_measurement_t, + pub reserved2: [u8; 32usize], + pub mr_signer: sgx_measurement_t, + pub reserved3: [u8; 32usize], + pub config_id: sgx_config_id_t, + pub isv_prod_id: sgx_prod_id_t, + pub isv_svn: sgx_isv_svn_t, + pub config_svn: sgx_config_svn_t, + pub reserved4: [u8; 42usize], + pub isv_family_id: sgx_isvfamily_id_t, + pub report_data: sgx_report_data_t, +} +#[test] +fn bindgen_test_layout__report_body_t() { + assert_eq!( + ::std::mem::size_of::<_report_body_t>(), + 384usize, + concat!("Size of: ", stringify!(_report_body_t)) + ); + assert_eq!( + ::std::mem::align_of::<_report_body_t>(), + 8usize, + concat!("Alignment of ", stringify!(_report_body_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).cpu_svn as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(cpu_svn) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).misc_select as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(misc_select) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).reserved1 as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved1) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).isv_ext_prod_id as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_ext_prod_id) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).attributes as *const _ as usize }, + 48usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(attributes) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).mr_enclave as *const _ as usize }, + 64usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(mr_enclave) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).reserved2 as *const _ as usize }, + 96usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved2) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).mr_signer as *const _ as usize }, + 128usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(mr_signer) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).reserved3 as *const _ as usize }, + 160usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved3) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).config_id as *const _ as usize }, + 192usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(config_id) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).isv_prod_id as *const _ as usize }, + 256usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_prod_id) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).isv_svn as *const _ as usize }, + 258usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_svn) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).config_svn as *const _ as usize }, + 260usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(config_svn) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).reserved4 as *const _ as usize }, + 262usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(reserved4) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).isv_family_id as *const _ as usize }, + 304usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(isv_family_id) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_body_t>())).report_data as *const _ as usize }, + 320usize, + concat!( + "Offset of field: ", + stringify!(_report_body_t), + "::", + stringify!(report_data) + ) + ); +} +impl Default for _report_body_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgx_report_body_t = _report_body_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _report_t { + pub body: sgx_report_body_t, + pub key_id: sgx_key_id_t, + pub mac: sgx_mac_t, +} +#[test] +fn bindgen_test_layout__report_t() { + assert_eq!( + ::std::mem::size_of::<_report_t>(), + 432usize, + concat!("Size of: ", stringify!(_report_t)) + ); + assert_eq!( + ::std::mem::align_of::<_report_t>(), + 8usize, + concat!("Alignment of ", stringify!(_report_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_t>())).body as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_report_t), + "::", + stringify!(body) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_t>())).key_id as *const _ as usize }, + 384usize, + concat!( + "Offset of field: ", + stringify!(_report_t), + "::", + stringify!(key_id) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_report_t>())).mac as *const _ as usize }, + 416usize, + concat!( + "Offset of field: ", + stringify!(_report_t), + "::", + stringify!(mac) + ) + ); +} +impl Default for _report_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgx_report_t = _report_t; +#[repr(C)] +#[repr(align(16))] +#[derive(Debug, Default, Copy, Clone, PartialEq)] +pub struct max_align_t { + pub __clang_max_align_nonce1: ::std::os::raw::c_longlong, + pub __bindgen_padding_0: u64, + pub __clang_max_align_nonce2: u128, +} +#[test] +fn bindgen_test_layout_max_align_t() { + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(max_align_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 16usize, + concat!("Alignment of ", stringify!(max_align_t)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).__clang_max_align_nonce1 as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(max_align_t), + "::", + stringify!(__clang_max_align_nonce1) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).__clang_max_align_nonce2 as *const _ as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(max_align_t), + "::", + stringify!(__clang_max_align_nonce2) + ) + ); +} +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct _sgx_kss_config_t { + pub config_id: sgx_config_id_t, + pub config_svn: sgx_config_svn_t, +} +#[test] +fn bindgen_test_layout__sgx_kss_config_t() { + assert_eq!( + ::std::mem::size_of::<_sgx_kss_config_t>(), + 66usize, + concat!("Size of: ", stringify!(_sgx_kss_config_t)) + ); + assert_eq!( + ::std::mem::align_of::<_sgx_kss_config_t>(), + 1usize, + concat!("Alignment of ", stringify!(_sgx_kss_config_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_sgx_kss_config_t>())).config_id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_kss_config_t), + "::", + stringify!(config_id) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_sgx_kss_config_t>())).config_svn as *const _ as usize }, + 64usize, + concat!( + "Offset of field: ", + stringify!(_sgx_kss_config_t), + "::", + stringify!(config_svn) + ) + ); +} +impl Default for _sgx_kss_config_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgx_kss_config_t = _sgx_kss_config_t; +pub type sgx_launch_token_t = [u8; 1024usize]; +extern "C" { + pub fn sgx_create_enclave( + file_name: *const ::std::os::raw::c_char, + debug: ::std::os::raw::c_int, + launch_token: *mut sgx_launch_token_t, + launch_token_updated: *mut ::std::os::raw::c_int, + enclave_id: *mut sgx_enclave_id_t, + misc_attr: *mut sgx_misc_attribute_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_create_enclave_ex( + file_name: *const ::std::os::raw::c_char, + debug: ::std::os::raw::c_int, + launch_token: *mut sgx_launch_token_t, + launch_token_updated: *mut ::std::os::raw::c_int, + enclave_id: *mut sgx_enclave_id_t, + misc_attr: *mut sgx_misc_attribute_t, + ex_features: u32, + ex_features_p: *mut *const ::std::os::raw::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_create_enclave_from_buffer_ex( + buffer: *mut u8, + buffer_size: usize, + debug: ::std::os::raw::c_int, + enclave_id: *mut sgx_enclave_id_t, + misc_attr: *mut sgx_misc_attribute_t, + ex_features: u32, + ex_features_p: *mut *const ::std::os::raw::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_create_encrypted_enclave( + file_name: *const ::std::os::raw::c_char, + debug: ::std::os::raw::c_int, + launch_token: *mut sgx_launch_token_t, + launch_token_updated: *mut ::std::os::raw::c_int, + enclave_id: *mut sgx_enclave_id_t, + misc_attr: *mut sgx_misc_attribute_t, + sealed_key: *mut u8, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_destroy_enclave(enclave_id: sgx_enclave_id_t) -> sgx_status_t; +} +extern "C" { + pub fn sgx_get_target_info( + enclave_id: sgx_enclave_id_t, + target_info: *mut sgx_target_info_t, + ) -> sgx_status_t; +} +pub type sgx_epid_group_id_t = [u8; 4usize]; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _spid_t { + pub id: [u8; 16usize], +} +#[test] +fn bindgen_test_layout__spid_t() { + assert_eq!( + ::std::mem::size_of::<_spid_t>(), + 16usize, + concat!("Size of: ", stringify!(_spid_t)) + ); + assert_eq!( + ::std::mem::align_of::<_spid_t>(), + 1usize, + concat!("Alignment of ", stringify!(_spid_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_spid_t>())).id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_spid_t), + "::", + stringify!(id) + ) + ); +} +pub type sgx_spid_t = _spid_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _basename_t { + pub name: [u8; 32usize], +} +#[test] +fn bindgen_test_layout__basename_t() { + assert_eq!( + ::std::mem::size_of::<_basename_t>(), + 32usize, + concat!("Size of: ", stringify!(_basename_t)) + ); + assert_eq!( + ::std::mem::align_of::<_basename_t>(), + 1usize, + concat!("Alignment of ", stringify!(_basename_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_basename_t>())).name as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_basename_t), + "::", + stringify!(name) + ) + ); +} +pub type sgx_basename_t = _basename_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _quote_nonce { + pub rand: [u8; 16usize], +} +#[test] +fn bindgen_test_layout__quote_nonce() { + assert_eq!( + ::std::mem::size_of::<_quote_nonce>(), + 16usize, + concat!("Size of: ", stringify!(_quote_nonce)) + ); + assert_eq!( + ::std::mem::align_of::<_quote_nonce>(), + 1usize, + concat!("Alignment of ", stringify!(_quote_nonce)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_quote_nonce>())).rand as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_quote_nonce), + "::", + stringify!(rand) + ) + ); +} +pub type sgx_quote_nonce_t = _quote_nonce; +pub const SGX_UNLINKABLE_SIGNATURE: sgx_quote_sign_type_t = 0; +pub const SGX_LINKABLE_SIGNATURE: sgx_quote_sign_type_t = 1; +pub type sgx_quote_sign_type_t = u32; +#[repr(C, packed)] +pub struct _quote_t { + pub version: u16, + pub sign_type: u16, + pub epid_group_id: sgx_epid_group_id_t, + pub qe_svn: sgx_isv_svn_t, + pub pce_svn: sgx_isv_svn_t, + pub xeid: u32, + pub basename: sgx_basename_t, + pub report_body: sgx_report_body_t, + pub signature_len: u32, + pub signature: __IncompleteArrayField, +} +#[test] +fn bindgen_test_layout__quote_t() { + assert_eq!( + ::std::mem::size_of::<_quote_t>(), + 436usize, + concat!("Size of: ", stringify!(_quote_t)) + ); + assert_eq!( + ::std::mem::align_of::<_quote_t>(), + 1usize, + concat!("Alignment of ", stringify!(_quote_t)) + ); +} +impl Default for _quote_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgx_quote_t = _quote_t; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _platform_info { + pub platform_info: [u8; 101usize], +} +#[test] +fn bindgen_test_layout__platform_info() { + assert_eq!( + ::std::mem::size_of::<_platform_info>(), + 101usize, + concat!("Size of: ", stringify!(_platform_info)) + ); + assert_eq!( + ::std::mem::align_of::<_platform_info>(), + 1usize, + concat!("Alignment of ", stringify!(_platform_info)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_platform_info>())).platform_info as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_platform_info), + "::", + stringify!(platform_info) + ) + ); +} +impl Default for _platform_info { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgx_platform_info_t = _platform_info; +#[repr(C, packed)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _update_info_bit { + pub ucodeUpdate: ::std::os::raw::c_int, + pub csmeFwUpdate: ::std::os::raw::c_int, + pub pswUpdate: ::std::os::raw::c_int, +} +#[test] +fn bindgen_test_layout__update_info_bit() { + assert_eq!( + ::std::mem::size_of::<_update_info_bit>(), + 12usize, + concat!("Size of: ", stringify!(_update_info_bit)) + ); + assert_eq!( + ::std::mem::align_of::<_update_info_bit>(), + 1usize, + concat!("Alignment of ", stringify!(_update_info_bit)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_update_info_bit>())).ucodeUpdate as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_update_info_bit), + "::", + stringify!(ucodeUpdate) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_update_info_bit>())).csmeFwUpdate as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(_update_info_bit), + "::", + stringify!(csmeFwUpdate) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_update_info_bit>())).pswUpdate as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(_update_info_bit), + "::", + stringify!(pswUpdate) + ) + ); +} +pub type sgx_update_info_bit_t = _update_info_bit; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _att_key_id_t { + pub att_key_id: [u8; 158usize], +} +#[test] +fn bindgen_test_layout__att_key_id_t() { + assert_eq!( + ::std::mem::size_of::<_att_key_id_t>(), + 158usize, + concat!("Size of: ", stringify!(_att_key_id_t)) + ); + assert_eq!( + ::std::mem::align_of::<_att_key_id_t>(), + 1usize, + concat!("Alignment of ", stringify!(_att_key_id_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_att_key_id_t>())).att_key_id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_att_key_id_t), + "::", + stringify!(att_key_id) + ) + ); +} +impl Default for _att_key_id_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgx_att_key_id_t = _att_key_id_t; +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct _qe_report_info_t { + pub nonce: sgx_quote_nonce_t, + pub app_enclave_target_info: sgx_target_info_t, + pub qe_report: sgx_report_t, +} +#[test] +fn bindgen_test_layout__qe_report_info_t() { + assert_eq!( + ::std::mem::size_of::<_qe_report_info_t>(), + 960usize, + concat!("Size of: ", stringify!(_qe_report_info_t)) + ); + assert_eq!( + ::std::mem::align_of::<_qe_report_info_t>(), + 1usize, + concat!("Alignment of ", stringify!(_qe_report_info_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_qe_report_info_t>())).nonce as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_qe_report_info_t), + "::", + stringify!(nonce) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<_qe_report_info_t>())).app_enclave_target_info as *const _ + as usize + }, + 16usize, + concat!( + "Offset of field: ", + stringify!(_qe_report_info_t), + "::", + stringify!(app_enclave_target_info) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_qe_report_info_t>())).qe_report as *const _ as usize }, + 528usize, + concat!( + "Offset of field: ", + stringify!(_qe_report_info_t), + "::", + stringify!(qe_report) + ) + ); +} +impl Default for _qe_report_info_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgx_qe_report_info_t = _qe_report_info_t; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_aes_gcm_mac { + pub data: [u8; 16usize], +} +#[test] +fn bindgen_test_layout_sgxsd_aes_gcm_mac() { + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(sgxsd_aes_gcm_mac)) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_aes_gcm_mac)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_aes_gcm_mac), + "::", + stringify!(data) + ) + ); +} +pub type sgxsd_aes_gcm_mac_t = sgxsd_aes_gcm_mac; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_aes_gcm_iv { + pub data: [u8; 12usize], +} +#[test] +fn bindgen_test_layout_sgxsd_aes_gcm_iv() { + assert_eq!( + ::std::mem::size_of::(), + 12usize, + concat!("Size of: ", stringify!(sgxsd_aes_gcm_iv)) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_aes_gcm_iv)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_aes_gcm_iv), + "::", + stringify!(data) + ) + ); +} +pub type sgxsd_aes_gcm_iv_t = sgxsd_aes_gcm_iv; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_aes_gcm_key { + pub data: [u8; 32usize], +} +#[test] +fn bindgen_test_layout_sgxsd_aes_gcm_key() { + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(sgxsd_aes_gcm_key)) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_aes_gcm_key)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_aes_gcm_key), + "::", + stringify!(data) + ) + ); +} +pub type sgxsd_aes_gcm_key_t = sgxsd_aes_gcm_key; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_curve25519_public_key { + pub x: [u8; 32usize], +} +#[test] +fn bindgen_test_layout_sgxsd_curve25519_public_key() { + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(sgxsd_curve25519_public_key)) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_curve25519_public_key)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).x as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_curve25519_public_key), + "::", + stringify!(x) + ) + ); +} +pub type sgxsd_curve25519_public_key_t = sgxsd_curve25519_public_key; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_request_negotiation_request { + pub client_pubkey: sgxsd_curve25519_public_key_t, +} +#[test] +fn bindgen_test_layout_sgxsd_request_negotiation_request() { + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(sgxsd_request_negotiation_request)) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!( + "Alignment of ", + stringify!(sgxsd_request_negotiation_request) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).client_pubkey as *const _ + as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_request), + "::", + stringify!(client_pubkey) + ) + ); +} +pub type sgxsd_request_negotiation_request_t = sgxsd_request_negotiation_request; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_pending_request_id { + pub data: [u8; 8usize], + pub iv: sgxsd_aes_gcm_iv_t, + pub mac: sgxsd_aes_gcm_mac_t, +} +#[test] +fn bindgen_test_layout_sgxsd_pending_request_id() { + assert_eq!( + ::std::mem::size_of::(), + 36usize, + concat!("Size of: ", stringify!(sgxsd_pending_request_id)) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_pending_request_id)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).data as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_pending_request_id), + "::", + stringify!(data) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).iv as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_pending_request_id), + "::", + stringify!(iv) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).mac as *const _ as usize }, + 20usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_pending_request_id), + "::", + stringify!(mac) + ) + ); +} +pub type sgxsd_pending_request_id_t = sgxsd_pending_request_id; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_request_negotiation_response { + pub server_static_pubkey: sgxsd_curve25519_public_key_t, + pub server_ephemeral_pubkey: sgxsd_curve25519_public_key_t, + pub encrypted_pending_request_id: sgxsd_request_negotiation_response__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_request_negotiation_response__bindgen_ty_1 { + pub data: [u8; 36usize], + pub iv: sgxsd_aes_gcm_iv_t, + pub mac: sgxsd_aes_gcm_mac_t, +} +#[test] +fn bindgen_test_layout_sgxsd_request_negotiation_response__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::(), + 64usize, + concat!( + "Size of: ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1) + ) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!( + "Alignment of ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).data + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1), + "::", + stringify!(data) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).iv + as *const _ as usize + }, + 36usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1), + "::", + stringify!(iv) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).mac + as *const _ as usize + }, + 48usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response__bindgen_ty_1), + "::", + stringify!(mac) + ) + ); +} +impl Default for sgxsd_request_negotiation_response__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout_sgxsd_request_negotiation_response() { + assert_eq!( + ::std::mem::size_of::(), + 128usize, + concat!("Size of: ", stringify!(sgxsd_request_negotiation_response)) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!( + "Alignment of ", + stringify!(sgxsd_request_negotiation_response) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).server_static_pubkey + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response), + "::", + stringify!(server_static_pubkey) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).server_ephemeral_pubkey + as *const _ as usize + }, + 32usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response), + "::", + stringify!(server_ephemeral_pubkey) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())) + .encrypted_pending_request_id as *const _ as usize + }, + 64usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_request_negotiation_response), + "::", + stringify!(encrypted_pending_request_id) + ) + ); +} +impl Default for sgxsd_request_negotiation_response { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgxsd_request_negotiation_response_t = sgxsd_request_negotiation_response; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_msg_tag { + pub __bindgen_anon_1: sgxsd_msg_tag__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union sgxsd_msg_tag__bindgen_ty_1 { + pub p_tag: *mut ::std::os::raw::c_void, + pub tag: u64, + _bindgen_union_align: u64, +} +#[test] +fn bindgen_test_layout_sgxsd_msg_tag__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(sgxsd_msg_tag__bindgen_ty_1)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_msg_tag__bindgen_ty_1)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).p_tag as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_tag__bindgen_ty_1), + "::", + stringify!(p_tag) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).tag as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_tag__bindgen_ty_1), + "::", + stringify!(tag) + ) + ); +} +impl Default for sgxsd_msg_tag__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout_sgxsd_msg_tag() { + assert_eq!( + ::std::mem::size_of::(), + 8usize, + concat!("Size of: ", stringify!(sgxsd_msg_tag)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_msg_tag)) + ); +} +impl Default for sgxsd_msg_tag { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgxsd_msg_tag_t = sgxsd_msg_tag; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_msg_header { + pub iv: sgxsd_aes_gcm_iv_t, + pub mac: sgxsd_aes_gcm_mac_t, + pub pending_request_id: sgxsd_pending_request_id_t, +} +#[test] +fn bindgen_test_layout_sgxsd_msg_header() { + assert_eq!( + ::std::mem::size_of::(), + 64usize, + concat!("Size of: ", stringify!(sgxsd_msg_header)) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_msg_header)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).iv as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_header), + "::", + stringify!(iv) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).mac as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_header), + "::", + stringify!(mac) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).pending_request_id as *const _ as usize + }, + 28usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_msg_header), + "::", + stringify!(pending_request_id) + ) + ); +} +pub type sgxsd_msg_header_t = sgxsd_msg_header; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_node_init_args { + pub pending_requests_table_order: u8, +} +#[test] +fn bindgen_test_layout_sgxsd_node_init_args() { + assert_eq!( + ::std::mem::size_of::(), + 1usize, + concat!("Size of: ", stringify!(sgxsd_node_init_args)) + ); + assert_eq!( + ::std::mem::align_of::(), + 1usize, + concat!("Alignment of ", stringify!(sgxsd_node_init_args)) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).pending_requests_table_order + as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_node_init_args), + "::", + stringify!(pending_requests_table_order) + ) + ); +} +pub type sgxsd_node_init_args_t = sgxsd_node_init_args; +pub type sgxsd_server_state_handle_t = u64; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct sgxsd_status { + pub ok: bool, + pub name: *const ::std::os::raw::c_char, + pub code: i64, +} +#[test] +fn bindgen_test_layout_sgxsd_status() { + assert_eq!( + ::std::mem::size_of::(), + 24usize, + concat!("Size of: ", stringify!(sgxsd_status)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_status)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).ok as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_status), + "::", + stringify!(ok) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).name as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_status), + "::", + stringify!(name) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).code as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_status), + "::", + stringify!(code) + ) + ); +} +impl Default for sgxsd_status { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgxsd_status_t = sgxsd_status; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sgxsd_enclave { + pub id: sgx_enclave_id_t, + pub __bindgen_anon_1: sgxsd_enclave__bindgen_ty_1, + pub launch_token: sgx_launch_token_t, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union sgxsd_enclave__bindgen_ty_1 { + pub gid: sgx_epid_group_id_t, + pub gid32: u32, + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout_sgxsd_enclave__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(sgxsd_enclave__bindgen_ty_1)) + ); + assert_eq!( + ::std::mem::align_of::(), + 4usize, + concat!("Alignment of ", stringify!(sgxsd_enclave__bindgen_ty_1)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).gid as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_enclave__bindgen_ty_1), + "::", + stringify!(gid) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::())).gid32 as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_enclave__bindgen_ty_1), + "::", + stringify!(gid32) + ) + ); +} +impl Default for sgxsd_enclave__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout_sgxsd_enclave() { + assert_eq!( + ::std::mem::size_of::(), + 1040usize, + concat!("Size of: ", stringify!(sgxsd_enclave)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(sgxsd_enclave)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).id as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_enclave), + "::", + stringify!(id) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).launch_token as *const _ as usize }, + 12usize, + concat!( + "Offset of field: ", + stringify!(sgxsd_enclave), + "::", + stringify!(launch_token) + ) + ); +} +impl Default for sgxsd_enclave { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type sgxsd_enclave_t = sgxsd_enclave; +pub type sgxsd_start_callback_t = ::std::option::Option< + unsafe extern "C" fn(arg1: sgxsd_enclave_t, arg2: *mut __va_list_tag) -> sgxsd_status_t, +>; +extern "C" { + pub fn sgxsd_start( + enclave_path: *const ::std::os::raw::c_char, + debug: bool, + p_launch_token: *const sgx_launch_token_t, + p_node_init_args: *const sgxsd_node_init_args_t, + p_callback: sgxsd_start_callback_t, + ... + ) -> sgxsd_status_t; +} +extern "C" { + pub fn sgxsd_get_next_quote( + enclave_id: sgx_enclave_id_t, + spid: sgx_spid_t, + p_sig_rl: *const u8, + sig_rl_size: u32, + p_quote: *mut sgx_quote_t, + quote_size: u32, + ) -> sgxsd_status_t; +} +pub const SGXSD_ERROR_PENDING_REQUEST_NOT_FOUND: sgxsd_status_code = 65537; +pub type sgxsd_status_code = u32; +pub use self::sgxsd_status_code as sgxsd_status_code_t; +pub type wint_t = ::std::os::raw::c_uint; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct __mbstate_t { + pub __count: ::std::os::raw::c_int, + pub __value: __mbstate_t__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union __mbstate_t__bindgen_ty_1 { + pub __wch: ::std::os::raw::c_uint, + pub __wchb: [::std::os::raw::c_char; 4usize], + _bindgen_union_align: u32, +} +#[test] +fn bindgen_test_layout___mbstate_t__bindgen_ty_1() { + assert_eq!( + ::std::mem::size_of::<__mbstate_t__bindgen_ty_1>(), + 4usize, + concat!("Size of: ", stringify!(__mbstate_t__bindgen_ty_1)) + ); + assert_eq!( + ::std::mem::align_of::<__mbstate_t__bindgen_ty_1>(), + 4usize, + concat!("Alignment of ", stringify!(__mbstate_t__bindgen_ty_1)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__mbstate_t__bindgen_ty_1>())).__wch as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t__bindgen_ty_1), + "::", + stringify!(__wch) + ) + ); + assert_eq!( + unsafe { + &(*(::std::ptr::null::<__mbstate_t__bindgen_ty_1>())).__wchb as *const _ as usize + }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t__bindgen_ty_1), + "::", + stringify!(__wchb) + ) + ); +} +impl Default for __mbstate_t__bindgen_ty_1 { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[test] +fn bindgen_test_layout___mbstate_t() { + assert_eq!( + ::std::mem::size_of::<__mbstate_t>(), + 8usize, + concat!("Size of: ", stringify!(__mbstate_t)) + ); + assert_eq!( + ::std::mem::align_of::<__mbstate_t>(), + 4usize, + concat!("Alignment of ", stringify!(__mbstate_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__mbstate_t>())).__count as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t), + "::", + stringify!(__count) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__mbstate_t>())).__value as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__mbstate_t), + "::", + stringify!(__value) + ) + ); +} +impl Default for __mbstate_t { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type mbstate_t = __mbstate_t; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _IO_FILE { + _unused: [u8; 0], +} +pub type __FILE = _IO_FILE; +pub type FILE = _IO_FILE; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct __locale_struct { + pub __locales: [*mut __locale_data; 13usize], + pub __ctype_b: *const ::std::os::raw::c_ushort, + pub __ctype_tolower: *const ::std::os::raw::c_int, + pub __ctype_toupper: *const ::std::os::raw::c_int, + pub __names: [*const ::std::os::raw::c_char; 13usize], +} +#[test] +fn bindgen_test_layout___locale_struct() { + assert_eq!( + ::std::mem::size_of::<__locale_struct>(), + 232usize, + concat!("Size of: ", stringify!(__locale_struct)) + ); + assert_eq!( + ::std::mem::align_of::<__locale_struct>(), + 8usize, + concat!("Alignment of ", stringify!(__locale_struct)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__locale_struct>())).__locales as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__locale_struct), + "::", + stringify!(__locales) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__locale_struct>())).__ctype_b as *const _ as usize }, + 104usize, + concat!( + "Offset of field: ", + stringify!(__locale_struct), + "::", + stringify!(__ctype_b) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__locale_struct>())).__ctype_tolower as *const _ as usize }, + 112usize, + concat!( + "Offset of field: ", + stringify!(__locale_struct), + "::", + stringify!(__ctype_tolower) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__locale_struct>())).__ctype_toupper as *const _ as usize }, + 120usize, + concat!( + "Offset of field: ", + stringify!(__locale_struct), + "::", + stringify!(__ctype_toupper) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__locale_struct>())).__names as *const _ as usize }, + 128usize, + concat!( + "Offset of field: ", + stringify!(__locale_struct), + "::", + stringify!(__names) + ) + ); +} +impl Default for __locale_struct { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +pub type __locale_t = *mut __locale_struct; +pub type locale_t = __locale_t; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct tm { + _unused: [u8; 0], +} +extern "C" { + pub fn wcscpy(__dest: *mut wchar_t, __src: *const wchar_t) -> *mut wchar_t; +} +extern "C" { + pub fn wcsncpy(__dest: *mut wchar_t, __src: *const wchar_t, __n: usize) -> *mut wchar_t; +} +extern "C" { + pub fn wcscat(__dest: *mut wchar_t, __src: *const wchar_t) -> *mut wchar_t; +} +extern "C" { + pub fn wcsncat(__dest: *mut wchar_t, __src: *const wchar_t, __n: usize) -> *mut wchar_t; +} +extern "C" { + pub fn wcscmp( + __s1: *const ::std::os::raw::c_int, + __s2: *const ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wcsncmp( + __s1: *const ::std::os::raw::c_int, + __s2: *const ::std::os::raw::c_int, + __n: ::std::os::raw::c_ulong, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wcscasecmp(__s1: *const wchar_t, __s2: *const wchar_t) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wcsncasecmp( + __s1: *const wchar_t, + __s2: *const wchar_t, + __n: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wcscasecmp_l( + __s1: *const wchar_t, + __s2: *const wchar_t, + __loc: locale_t, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wcsncasecmp_l( + __s1: *const wchar_t, + __s2: *const wchar_t, + __n: usize, + __loc: locale_t, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wcscoll(__s1: *const wchar_t, __s2: *const wchar_t) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wcsxfrm(__s1: *mut wchar_t, __s2: *const wchar_t, __n: usize) -> usize; +} +extern "C" { + pub fn wcscoll_l( + __s1: *const wchar_t, + __s2: *const wchar_t, + __loc: locale_t, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wcsxfrm_l( + __s1: *mut wchar_t, + __s2: *const wchar_t, + __n: usize, + __loc: locale_t, + ) -> usize; +} +extern "C" { + pub fn wcsdup(__s: *const wchar_t) -> *mut wchar_t; +} +extern "C" { + pub fn wcschr( + __wcs: *const ::std::os::raw::c_int, + __wc: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_int; +} +extern "C" { + pub fn wcsrchr(__wcs: *const wchar_t, __wc: wchar_t) -> *mut wchar_t; +} +extern "C" { + pub fn wcscspn(__wcs: *const wchar_t, __reject: *const wchar_t) -> usize; +} +extern "C" { + pub fn wcsspn(__wcs: *const wchar_t, __accept: *const wchar_t) -> usize; +} +extern "C" { + pub fn wcspbrk(__wcs: *const wchar_t, __accept: *const wchar_t) -> *mut wchar_t; +} +extern "C" { + pub fn wcsstr(__haystack: *const wchar_t, __needle: *const wchar_t) -> *mut wchar_t; +} +extern "C" { + pub fn wcstok( + __s: *mut wchar_t, + __delim: *const wchar_t, + __ptr: *mut *mut wchar_t, + ) -> *mut wchar_t; +} +extern "C" { + pub fn wcslen(__s: *const ::std::os::raw::c_int) -> ::std::os::raw::c_ulong; +} +extern "C" { + pub fn wcsnlen(__s: *const wchar_t, __maxlen: usize) -> usize; +} +extern "C" { + pub fn wmemchr( + __s: *const ::std::os::raw::c_int, + __c: ::std::os::raw::c_int, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_int; +} +extern "C" { + pub fn wmemcmp( + __s1: *const ::std::os::raw::c_int, + __s2: *const ::std::os::raw::c_int, + __n: ::std::os::raw::c_ulong, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wmemcpy(__s1: *mut wchar_t, __s2: *const wchar_t, __n: usize) -> *mut wchar_t; +} +extern "C" { + pub fn wmemmove(__s1: *mut wchar_t, __s2: *const wchar_t, __n: usize) -> *mut wchar_t; +} +extern "C" { + pub fn wmemset(__s: *mut wchar_t, __c: wchar_t, __n: usize) -> *mut wchar_t; +} +extern "C" { + pub fn btowc(__c: ::std::os::raw::c_int) -> wint_t; +} +extern "C" { + pub fn wctob(__c: wint_t) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mbsinit(__ps: *const mbstate_t) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn mbrtowc( + __pwc: *mut wchar_t, + __s: *const ::std::os::raw::c_char, + __n: usize, + __p: *mut mbstate_t, + ) -> usize; +} +extern "C" { + pub fn wcrtomb(__s: *mut ::std::os::raw::c_char, __wc: wchar_t, __ps: *mut mbstate_t) -> usize; +} +extern "C" { + pub fn __mbrlen(__s: *const ::std::os::raw::c_char, __n: usize, __ps: *mut mbstate_t) -> usize; +} +extern "C" { + pub fn mbrlen(__s: *const ::std::os::raw::c_char, __n: usize, __ps: *mut mbstate_t) -> usize; +} +extern "C" { + pub fn mbsrtowcs( + __dst: *mut wchar_t, + __src: *mut *const ::std::os::raw::c_char, + __len: usize, + __ps: *mut mbstate_t, + ) -> usize; +} +extern "C" { + pub fn wcsrtombs( + __dst: *mut ::std::os::raw::c_char, + __src: *mut *const wchar_t, + __len: usize, + __ps: *mut mbstate_t, + ) -> usize; +} +extern "C" { + pub fn mbsnrtowcs( + __dst: *mut wchar_t, + __src: *mut *const ::std::os::raw::c_char, + __nmc: usize, + __len: usize, + __ps: *mut mbstate_t, + ) -> usize; +} +extern "C" { + pub fn wcsnrtombs( + __dst: *mut ::std::os::raw::c_char, + __src: *mut *const wchar_t, + __nwc: usize, + __len: usize, + __ps: *mut mbstate_t, + ) -> usize; +} +extern "C" { + pub fn wcstod(__nptr: *const wchar_t, __endptr: *mut *mut wchar_t) -> f64; +} +extern "C" { + pub fn wcstof(__nptr: *const wchar_t, __endptr: *mut *mut wchar_t) -> f32; +} +extern "C" { + pub fn wcstold(__nptr: *const wchar_t, __endptr: *mut *mut wchar_t) -> u128; +} +extern "C" { + pub fn wcstol( + __nptr: *const wchar_t, + __endptr: *mut *mut wchar_t, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_long; +} +extern "C" { + pub fn wcstoul( + __nptr: *const wchar_t, + __endptr: *mut *mut wchar_t, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_ulong; +} +extern "C" { + pub fn wcstoll( + __nptr: *const wchar_t, + __endptr: *mut *mut wchar_t, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_longlong; +} +extern "C" { + pub fn wcstoull( + __nptr: *const wchar_t, + __endptr: *mut *mut wchar_t, + __base: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_ulonglong; +} +extern "C" { + pub fn wcpcpy(__dest: *mut wchar_t, __src: *const wchar_t) -> *mut wchar_t; +} +extern "C" { + pub fn wcpncpy(__dest: *mut wchar_t, __src: *const wchar_t, __n: usize) -> *mut wchar_t; +} +extern "C" { + pub fn open_wmemstream(__bufloc: *mut *mut wchar_t, __sizeloc: *mut usize) -> *mut __FILE; +} +extern "C" { + pub fn fwide(__fp: *mut __FILE, __mode: ::std::os::raw::c_int) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn fwprintf(__stream: *mut __FILE, __format: *const wchar_t, ...) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wprintf(__format: *const wchar_t, ...) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn swprintf( + __s: *mut wchar_t, + __n: usize, + __format: *const wchar_t, + ... + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn vfwprintf( + __s: *mut __FILE, + __format: *const wchar_t, + __arg: *mut __va_list_tag, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn vwprintf(__format: *const wchar_t, __arg: *mut __va_list_tag) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn vswprintf( + __s: *mut wchar_t, + __n: usize, + __format: *const wchar_t, + __arg: *mut __va_list_tag, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn fwscanf(__stream: *mut __FILE, __format: *const wchar_t, ...) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn wscanf(__format: *const wchar_t, ...) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn swscanf(__s: *const wchar_t, __format: *const wchar_t, ...) -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "\u{1}__isoc99_fwscanf"] + pub fn fwscanf1(__stream: *mut __FILE, __format: *const wchar_t, ...) -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "\u{1}__isoc99_wscanf"] + pub fn wscanf1(__format: *const wchar_t, ...) -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "\u{1}__isoc99_swscanf"] + pub fn swscanf1(__s: *const wchar_t, __format: *const wchar_t, ...) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn vfwscanf( + __s: *mut __FILE, + __format: *const wchar_t, + __arg: *mut __va_list_tag, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn vwscanf(__format: *const wchar_t, __arg: *mut __va_list_tag) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn vswscanf( + __s: *const wchar_t, + __format: *const wchar_t, + __arg: *mut __va_list_tag, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "\u{1}__isoc99_vfwscanf"] + pub fn vfwscanf1( + __s: *mut __FILE, + __format: *const wchar_t, + __arg: *mut __va_list_tag, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "\u{1}__isoc99_vwscanf"] + pub fn vwscanf1(__format: *const wchar_t, __arg: *mut __va_list_tag) -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "\u{1}__isoc99_vswscanf"] + pub fn vswscanf1( + __s: *const wchar_t, + __format: *const wchar_t, + __arg: *mut __va_list_tag, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn fgetwc(__stream: *mut __FILE) -> wint_t; +} +extern "C" { + pub fn getwc(__stream: *mut __FILE) -> wint_t; +} +extern "C" { + pub fn getwchar() -> wint_t; +} +extern "C" { + pub fn fputwc(__wc: wchar_t, __stream: *mut __FILE) -> wint_t; +} +extern "C" { + pub fn putwc(__wc: wchar_t, __stream: *mut __FILE) -> wint_t; +} +extern "C" { + pub fn putwchar(__wc: wchar_t) -> wint_t; +} +extern "C" { + pub fn fgetws( + __ws: *mut wchar_t, + __n: ::std::os::raw::c_int, + __stream: *mut __FILE, + ) -> *mut wchar_t; +} +extern "C" { + pub fn fputws(__ws: *const wchar_t, __stream: *mut __FILE) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn ungetwc(__wc: wint_t, __stream: *mut __FILE) -> wint_t; +} +extern "C" { + pub fn wcsftime( + __s: *mut wchar_t, + __maxsize: usize, + __format: *const wchar_t, + __tp: *const tm, + ) -> usize; +} +extern "C" { + pub fn memcpy( + __dest: *mut ::std::os::raw::c_void, + __src: *const ::std::os::raw::c_void, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn memmove( + __dest: *mut ::std::os::raw::c_void, + __src: *const ::std::os::raw::c_void, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn memccpy( + __dest: *mut ::std::os::raw::c_void, + __src: *const ::std::os::raw::c_void, + __c: ::std::os::raw::c_int, + __n: usize, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn memset( + __s: *mut ::std::os::raw::c_void, + __c: ::std::os::raw::c_int, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn memcmp( + __s1: *const ::std::os::raw::c_void, + __s2: *const ::std::os::raw::c_void, + __n: ::std::os::raw::c_ulong, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn memchr( + __s: *const ::std::os::raw::c_void, + __c: ::std::os::raw::c_int, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn strcpy( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strncpy( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strcat( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strncat( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strcmp( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strncmp( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strcoll( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strxfrm( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> ::std::os::raw::c_ulong; +} +extern "C" { + pub fn strcoll_l( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + __l: locale_t, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strxfrm_l( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + __n: usize, + __l: locale_t, + ) -> usize; +} +extern "C" { + pub fn strdup(__s: *const ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strndup( + __string: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strchr( + __s: *const ::std::os::raw::c_char, + __c: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strrchr( + __s: *const ::std::os::raw::c_char, + __c: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strcspn( + __s: *const ::std::os::raw::c_char, + __reject: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_ulong; +} +extern "C" { + pub fn strspn( + __s: *const ::std::os::raw::c_char, + __accept: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_ulong; +} +extern "C" { + pub fn strpbrk( + __s: *const ::std::os::raw::c_char, + __accept: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strstr( + __haystack: *const ::std::os::raw::c_char, + __needle: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strtok( + __s: *mut ::std::os::raw::c_char, + __delim: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn __strtok_r( + __s: *mut ::std::os::raw::c_char, + __delim: *const ::std::os::raw::c_char, + __save_ptr: *mut *mut ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strtok_r( + __s: *mut ::std::os::raw::c_char, + __delim: *const ::std::os::raw::c_char, + __save_ptr: *mut *mut ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strlen(__s: *const ::std::os::raw::c_char) -> ::std::os::raw::c_ulong; +} +extern "C" { + pub fn strnlen(__string: *const ::std::os::raw::c_char, __maxlen: usize) -> usize; +} +extern "C" { + pub fn strerror(__errnum: ::std::os::raw::c_int) -> *mut ::std::os::raw::c_char; +} +extern "C" { + #[link_name = "\u{1}__xpg_strerror_r"] + pub fn strerror_r( + __errnum: ::std::os::raw::c_int, + __buf: *mut ::std::os::raw::c_char, + __buflen: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strerror_l( + __errnum: ::std::os::raw::c_int, + __l: locale_t, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn bcmp( + __s1: *const ::std::os::raw::c_void, + __s2: *const ::std::os::raw::c_void, + __n: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn bcopy( + __src: *const ::std::os::raw::c_void, + __dest: *mut ::std::os::raw::c_void, + __n: usize, + ); +} +extern "C" { + pub fn bzero(__s: *mut ::std::os::raw::c_void, __n: ::std::os::raw::c_ulong); +} +extern "C" { + pub fn index( + __s: *const ::std::os::raw::c_char, + __c: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn rindex( + __s: *const ::std::os::raw::c_char, + __c: ::std::os::raw::c_int, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn ffs(__i: ::std::os::raw::c_int) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn ffsl(__l: ::std::os::raw::c_long) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn ffsll(__ll: ::std::os::raw::c_longlong) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strcasecmp( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strncasecmp( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strcasecmp_l( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + __loc: locale_t, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn strncasecmp_l( + __s1: *const ::std::os::raw::c_char, + __s2: *const ::std::os::raw::c_char, + __n: usize, + __loc: locale_t, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn explicit_bzero(__s: *mut ::std::os::raw::c_void, __n: usize); +} +extern "C" { + pub fn strsep( + __stringp: *mut *mut ::std::os::raw::c_char, + __delim: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn strsignal(__sig: ::std::os::raw::c_int) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn __stpcpy( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn stpcpy( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn __stpncpy( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + __n: usize, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn stpncpy( + __dest: *mut ::std::os::raw::c_char, + __src: *const ::std::os::raw::c_char, + __n: ::std::os::raw::c_ulong, + ) -> *mut ::std::os::raw::c_char; +} +extern "C" { + pub fn sgx_ocalloc(size: usize) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn sgx_ocalloc_switchless(size: usize) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn sgx_ocfree(); +} +extern "C" { + pub fn sgx_ocfree_switchless(); +} +extern "C" { + pub fn sgx_ecall( + eid: sgx_enclave_id_t, + index: ::std::os::raw::c_int, + ocall_table: *const ::std::os::raw::c_void, + ms: *mut ::std::os::raw::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_ecall_switchless( + eid: sgx_enclave_id_t, + index: ::std::os::raw::c_int, + ocall_table: *const ::std::os::raw::c_void, + ms: *mut ::std::os::raw::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_ocall( + index: ::std::os::raw::c_uint, + ms: *mut ::std::os::raw::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_ocall_switchless( + index: ::std::os::raw::c_uint, + ms: *mut ::std::os::raw::c_void, + ) -> sgx_status_t; +} +extern "C" { + pub fn kbupd_enclave_ocall_recv_enclave_msg(data: *const u8, data_size: usize); +} +extern "C" { + pub fn kbupd_enclave_ocall_alloc(size: *mut usize) -> *mut ::std::os::raw::c_void; +} +extern "C" { + pub fn kbupd_enclave_ocall_panic(msg: *const u8, msg_size: usize); +} +extern "C" { + pub fn sgxsd_ocall_reply( + reply_header: *const sgxsd_msg_header_t, + reply_data: *const u8, + reply_data_size: usize, + msg_tag: sgxsd_msg_tag_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_oc_cpuidex( + cpuinfo: *mut ::std::os::raw::c_int, + leaf: ::std::os::raw::c_int, + subleaf: ::std::os::raw::c_int, + ); +} +extern "C" { + pub fn sgx_thread_wait_untrusted_event_ocall( + self_: *const ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn sgx_thread_set_untrusted_event_ocall( + waiter: *const ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn sgx_thread_setwait_untrusted_events_ocall( + waiter: *const ::std::os::raw::c_void, + self_: *const ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn sgx_thread_set_multiple_untrusted_events_ocall( + waiters: *mut *const ::std::os::raw::c_void, + total: usize, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn kbupd_enclave_recv_untrusted_msg( + eid: sgx_enclave_id_t, + data: *const u8, + data_size: usize, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_node_init( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + p_args: *const sgxsd_node_init_args_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_get_next_report( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + qe_target_info: sgx_target_info_t, + p_report: *mut sgx_report_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_set_current_quote( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_negotiate_request( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + p_request: *const sgxsd_request_negotiation_request_t, + p_response: *mut sgxsd_request_negotiation_response_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_server_start( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + p_args: *const sgxsd_server_init_args_t, + state_handle: sgxsd_server_state_handle_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_server_call( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + p_args: *const sgxsd_server_handle_call_args_t, + msg_header: *const sgxsd_msg_header_t, + msg_data: *mut u8, + msg_size: usize, + msg_tag: sgxsd_msg_tag_t, + state_handle: sgxsd_server_state_handle_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgxsd_enclave_server_stop( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + p_args: *const sgxsd_server_terminate_args_t, + state_handle: sgxsd_server_state_handle_t, + ) -> sgx_status_t; +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct _sgx_ps_cap_t { + pub ps_cap0: u32, + pub ps_cap1: u32, +} +#[test] +fn bindgen_test_layout__sgx_ps_cap_t() { + assert_eq!( + ::std::mem::size_of::<_sgx_ps_cap_t>(), + 8usize, + concat!("Size of: ", stringify!(_sgx_ps_cap_t)) + ); + assert_eq!( + ::std::mem::align_of::<_sgx_ps_cap_t>(), + 4usize, + concat!("Alignment of ", stringify!(_sgx_ps_cap_t)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_sgx_ps_cap_t>())).ps_cap0 as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(_sgx_ps_cap_t), + "::", + stringify!(ps_cap0) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<_sgx_ps_cap_t>())).ps_cap1 as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(_sgx_ps_cap_t), + "::", + stringify!(ps_cap1) + ) + ); +} +pub type sgx_ps_cap_t = _sgx_ps_cap_t; +extern "C" { + pub fn sgx_init_quote( + p_target_info: *mut sgx_target_info_t, + p_gid: *mut sgx_epid_group_id_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_calc_quote_size( + p_sig_rl: *const u8, + sig_rl_size: u32, + p_quote_size: *mut u32, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_get_quote_size(p_sig_rl: *const u8, p_quote_size: *mut u32) -> sgx_status_t; +} +extern "C" { + pub fn sgx_get_quote( + p_report: *const sgx_report_t, + quote_type: sgx_quote_sign_type_t, + p_spid: *const sgx_spid_t, + p_nonce: *const sgx_quote_nonce_t, + p_sig_rl: *const u8, + sig_rl_size: u32, + p_qe_report: *mut sgx_report_t, + p_quote: *mut sgx_quote_t, + quote_size: u32, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_get_ps_cap(p_sgx_ps_cap: *mut sgx_ps_cap_t) -> sgx_status_t; +} +extern "C" { + pub fn sgx_get_whitelist_size(p_whitelist_size: *mut u32) -> sgx_status_t; +} +extern "C" { + pub fn sgx_get_whitelist(p_whitelist: *mut u8, whitelist_size: u32) -> sgx_status_t; +} +extern "C" { + pub fn sgx_get_extended_epid_group_id(p_extended_epid_group_id: *mut u32) -> sgx_status_t; +} +extern "C" { + pub fn sgx_report_attestation_status( + p_platform_info: *const sgx_platform_info_t, + attestation_status: ::std::os::raw::c_int, + p_update_info: *mut sgx_update_info_bit_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_register_wl_cert_chain( + p_wl_cert_chain: *mut u8, + wl_cert_chain_size: u32, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_select_att_key_id( + p_att_key_id_list: *const u8, + att_key_id_list_size: u32, + pp_selected_key_id: *mut *mut sgx_att_key_id_t, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_init_quote_ex( + p_att_key_id: *const sgx_att_key_id_t, + p_qe_target_info: *mut sgx_target_info_t, + refresh_att_key: bool, + p_pub_key_id_size: *mut usize, + p_pub_key_id: *mut u8, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_get_quote_size_ex( + p_att_key_id: *const sgx_att_key_id_t, + p_quote_size: *mut u32, + ) -> sgx_status_t; +} +extern "C" { + pub fn sgx_get_quote_ex( + p_app_report: *const sgx_report_t, + p_att_key_id: *const sgx_att_key_id_t, + p_qe_report_info: *mut sgx_qe_report_info_t, + p_quote: *mut u8, + quote_size: u32, + ) -> sgx_status_t; +} +pub type __builtin_va_list = [__va_list_tag; 1usize]; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct __va_list_tag { + pub gp_offset: ::std::os::raw::c_uint, + pub fp_offset: ::std::os::raw::c_uint, + pub overflow_arg_area: *mut ::std::os::raw::c_void, + pub reg_save_area: *mut ::std::os::raw::c_void, +} +#[test] +fn bindgen_test_layout___va_list_tag() { + assert_eq!( + ::std::mem::size_of::<__va_list_tag>(), + 24usize, + concat!("Size of: ", stringify!(__va_list_tag)) + ); + assert_eq!( + ::std::mem::align_of::<__va_list_tag>(), + 8usize, + concat!("Alignment of ", stringify!(__va_list_tag)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__va_list_tag>())).gp_offset as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(gp_offset) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__va_list_tag>())).fp_offset as *const _ as usize }, + 4usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(fp_offset) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__va_list_tag>())).overflow_arg_area as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(overflow_arg_area) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::<__va_list_tag>())).reg_save_area as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(__va_list_tag), + "::", + stringify!(reg_save_area) + ) + ); +} +impl Default for __va_list_tag { + fn default() -> Self { + unsafe { ::std::mem::zeroed() } + } +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub struct __locale_data { + pub _address: u8, +} diff --git a/service/kbupd/src/enclave/ffi/ecalls.rs b/service/kbupd/src/enclave/ffi/ecalls.rs new file mode 100644 index 0000000..ef1aff2 --- /dev/null +++ b/service/kbupd/src/enclave/ffi/ecalls.rs @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use prost::{Message}; + +use crate::protobufs::kbupd::*; + +use super::ocalls; +use super::sgx::*; +use super::sgxsd::*; + +use super::bindgen_wrapper::{kbupd_enclave_recv_untrusted_msg}; + +pub fn kbupd_send(enclave_id: SgxEnclaveId, messages: Vec) -> SgxsdResult> { + let batch = UntrustedMessageBatch { + messages, + }; + let mut data = Vec::with_capacity(batch.encoded_len()); + batch.encode(&mut data).unwrap(); + kbupd_send_raw(enclave_id, &data) +} +pub fn kbupd_send_raw(enclave_id: SgxEnclaveId, data: &[u8]) -> SgxsdResult> { + sgxsd_res(|_| unsafe { kbupd_enclave_recv_untrusted_msg(enclave_id, data.as_ptr(), data.len()) }, + "kbupd_enclave_recv_untrusted_msg")?; + Ok(ocalls::take_enclave_messages()) +} diff --git a/service/kbupd/src/enclave/ffi/mod.rs b/service/kbupd/src/enclave/ffi/mod.rs new file mode 100644 index 0000000..b8f7d8a --- /dev/null +++ b/service/kbupd/src/enclave/ffi/mod.rs @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#[allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals, improper_ctypes)] +mod bindgen_wrapper; + +pub mod ecalls; +pub mod ocalls; +pub mod sgx; +pub mod sgxsd; diff --git a/service/kbupd/src/enclave/ffi/ocalls.rs b/service/kbupd/src/enclave/ffi/ocalls.rs new file mode 100644 index 0000000..3ddf519 --- /dev/null +++ b/service/kbupd/src/enclave/ffi/ocalls.rs @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::mem; + +use prost::{Message}; + +use crate::*; +use crate::protobufs::kbupd::*; + +use super::sgx::*; +use super::sgxsd::*; + +use super::bindgen_wrapper::{ + sgx_status_t, + sgxsd_msg_tag_t, +}; + +// +// kbupd-specific ocalls +// + +thread_local! { + static ENCLAVE_MESSAGES: std::cell::RefCell> = Default::default(); +} + +#[must_use] +pub fn take_enclave_messages() -> Vec { + ENCLAVE_MESSAGES.with(|cell| mem::replace(&mut *cell.borrow_mut(), Default::default())) +} + +#[no_mangle] +pub extern "C" fn kbupd_enclave_ocall_recv_enclave_msg(p_data: *const u8, data_size: usize) { + let data = unsafe { std::slice::from_raw_parts(p_data, data_size) }; + + match EnclaveMessageBatch::decode(data) { + Ok(batch) => { + ENCLAVE_MESSAGES.with(|cell| cell.borrow_mut().extend(batch.messages)); + } + Err(err) => { + error!("enclave message decode error: {:?}", err); + } + } +} + +#[no_mangle] +pub extern "C" fn kbupd_enclave_ocall_alloc(p_size: *mut usize) -> *mut libc::c_void { + if p_size.is_null() { + return std::ptr::null_mut(); + } + let want_size = unsafe { *p_size }; + info!("allocating enclave storage of size {}", want_size); + + let mut data_vec: Vec = Vec::with_capacity(want_size); + let data: *mut u8 = data_vec.as_mut_ptr(); + let size: usize = data_vec.capacity(); + std::mem::forget(data_vec); + + match unsafe { libc::mlock(data as *const libc::c_void, size) } { + 0 => (), + mlock_res => { + warn!("could not mlock enclave storage: {}", mlock_res); + } + } + + unsafe { *p_size = size }; + data as *mut libc::c_void +} + +#[no_mangle] +pub extern "C" fn kbupd_enclave_ocall_panic(p_msg: *const u8, msg_size: usize) { + use std::io::{Write}; + + let msg = if !p_msg.is_null() { + unsafe { std::slice::from_raw_parts(p_msg, msg_size) } + } else { + b"enclave panic" + }; + + let mut stderr = std::io::stderr(); + let _ignore = write!(stderr, "{}", String::from_utf8_lossy(msg)); + let _ignore = stderr.flush(); +} + +// +// sgxsd ocalls +// + + +#[no_mangle] +pub extern "C" fn sgxsd_ocall_reply( + p_header: *const SgxsdMessageHeader, + p_data: *const u8, + data_size: usize, + raw_tag: sgxsd_msg_tag_t, +) -> sgx_status_t { + // note: we take ownership of MessageTag here and release it + match (unsafe { MessageTag::from_tag(raw_tag) }, unsafe { p_header.as_ref() }, p_data.is_null()) { + (Some(MessageTag { callback }), Some(header), false) => { + let data = unsafe { std::slice::from_raw_parts(p_data, data_size) }.to_vec(); + callback(Ok(MessageReply { iv: header.iv, mac: header.mac, data })); + SgxStatus::Success.into() + } + _ => SgxError::InvalidParameter.into(), + } +} diff --git a/service/kbupd/src/enclave/ffi/sgx.rs b/service/kbupd/src/enclave/ffi/sgx.rs new file mode 100644 index 0000000..3f0b396 --- /dev/null +++ b/service/kbupd/src/enclave/ffi/sgx.rs @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::convert::{TryInto}; +use std::fmt; + +use num_traits::{FromPrimitive}; + +use super::sgxsd::*; + +use super::bindgen_wrapper::{ + sgx_calc_quote_size, + sgx_create_enclave, + sgx_get_quote, + sgx_init_quote, + sgx_target_info_t, + sgx_report_t, + sgx_spid_t, + sgx_status_t, + sgx_quote_t, + SGX_SUCCESS, + SGX_UNLINKABLE_SIGNATURE, +}; + +pub use super::bindgen_wrapper::{ + sgx_enclave_id_t as SgxEnclaveId, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum SgxStatus { + Success, + Error(SgxError), + Unknown(u32), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, num_derive::FromPrimitive)] +pub enum SgxError { + Unexpected = 1, + InvalidParameter = 2, + OutOfMemory = 3, + EnclaveLost = 4, + InvalidState = 5, + FeatureNotSupported = 8, + InvalidFunction = 4097, + OutOfTcs = 4099, + EnclaveCrashed = 4102, + EcallNotAllowed = 4103, + OcallNotAllowed = 4104, + StackOverrun = 4105, + UndefinedSymbol = 8192, + InvalidEnclave = 8193, + InvalidEnclaveId = 8194, + InvalidSignature = 8195, + NdebugEnclave = 8196, + OutOfEpc = 8197, + NoDevice = 8198, + MemoryMapConflict = 8199, + InvalidMetadata = 8201, + DeviceBusy = 8204, + InvalidVersion = 8205, + ModeIncompatible = 8206, + EnclaveFileAccess = 8207, + InvalidMisc = 8208, + InvalidLaunchToken = 8209, + MacMismatch = 12289, + InvalidAttribute = 12290, + InvalidCpusvn = 12291, + InvalidIsvsvn = 12292, + InvalidKeyname = 12293, + ServiceUnavailable = 16385, + ServiceTimeout = 16386, + AeInvalidEpidblob = 16387, + ServiceInvalidPrivilege = 16388, + EpidMemberRevoked = 16389, + UpdateNeeded = 16390, + NetworkFailure = 16391, + AeSessionInvalid = 16392, + Busy = 16394, + McNotFound = 16396, + McNoAccessRight = 16397, + McUsedUp = 16398, + McOverQuota = 16399, + KdfMismatch = 16401, + UnrecognizedPlatform = 16402, + NoPrivilege = 20482, + PclEncrypted = 24577, + PclNotEncrypted = 24578, + PclMacMismatch = 24579, + PclShaMismatch = 24580, + PclGuidMismatch = 24581, + FileBadStatus = 28673, + FileNoKeyId = 28674, + FileNameMismatch = 28675, + FileNotSgxFile = 28676, + FileCantOpenRecoveryFile = 28677, + FileCantWriteRecoveryFile = 28678, + FileRecoveryNeeded = 28679, + FileFlushFailed = 28680, + FileCloseFailed = 28681, + UnsupportedAttKeyId = 32769, + AttKeyCertificationFailure = 32770, + AttKeyUninitialized = 32771, + InvalidAttKeyCertData = 32772, + EnclaveCreateInterrupted = 61441, + SgxsdPendingRequestNotFound = 65537, +} + +// +// free functions +// + +pub fn create_enclave(enclave_filename: &str, debug: bool) -> SgxsdResult { + let enclave_filename_cstr = std::ffi::CString::new(enclave_filename).unwrap(); + let mut launch_token: [u8; 1024] = [0; 1024]; + let mut launch_token_updated: i32 = Default::default(); + let mut enclave_id: SgxEnclaveId = Default::default(); + sgxsd_res(|_| unsafe { sgx_create_enclave( + enclave_filename_cstr.as_ptr(), + debug as std::os::raw::c_int, + &mut launch_token, + &mut launch_token_updated, + &mut enclave_id, + std::ptr::null_mut() + )}, "sgx_create_enclave") + .map(|()| enclave_id) +} + +pub fn get_gid() -> SgxsdResult { + let mut qe_target_info: sgx_target_info_t = Default::default(); + let mut gid: [u8; 4] = Default::default(); + sgxsd_res(|_| unsafe { sgx_init_quote(&mut qe_target_info, &mut gid) }, "sgx_init_quote")?; + Ok(u32::from_ne_bytes(gid)) +} + +pub fn get_qe_target_info() -> SgxsdResult { + let mut qe_target_info: sgx_target_info_t = Default::default(); + let mut gid: [u8; 4] = Default::default(); + sgxsd_res(|_| unsafe { sgx_init_quote(&mut qe_target_info, &mut gid) }, "sgx_init_quote")?; + Ok(qe_target_info) +} + +pub fn get_quote(report: &[u8], spid: &[u8], sig_rl: &[u8]) -> SgxsdResult> { + let spid_struct = sgx_spid_t { + id: spid.try_into().map_err(|_| SgxsdError { kind: SgxsdErrorKind::Sgx, status: SgxError::InvalidParameter.into(), name: "get_quote_spid" })?, + }; + if report.len() == std::mem::size_of::() { + let report = unsafe { std::ptr::read_unaligned(report.as_ptr() as *const sgx_report_t) }; + + let (p_sig_rl, sig_rl_len) = get_sig_rl_ptr(sig_rl); + let mut quote_size: u32 = Default::default(); + sgxsd_res(|_| unsafe { sgx_calc_quote_size(p_sig_rl, sig_rl_len, &mut quote_size) }, "sgx_calc_quote_size")?; + + if (quote_size as usize) >= std::mem::size_of::() { + let mut quote = vec![0; quote_size as usize]; + sgxsd_res(|_| unsafe { + sgx_get_quote(&report, SGX_UNLINKABLE_SIGNATURE, &spid_struct, + std::ptr::null(), + p_sig_rl, sig_rl_len, + std::ptr::null_mut(), + quote.as_mut_ptr() as *mut sgx_quote_t, quote_size) + }, "sgx_get_quote")?; + Ok(quote) + } else { + Err(SgxsdError { kind: SgxsdErrorKind::Sgx, status: SgxError::Unexpected.into(), name: "bad_quote_size" }) + } + } else { + Err(SgxsdError { kind: SgxsdErrorKind::Sgx, status: SgxError::InvalidParameter.into(), name: "get_quote" }) + } +} + +pub fn get_sig_rl_ptr(sig_rl: &[u8]) -> (*const u8, u32) { + match sig_rl.len() { + 0 => (std::ptr::null(), 0), + len if len < (u32::max_value() as usize) => (sig_rl.as_ptr(), len as u32), + _ => (std::ptr::null(), 0) + } +} + +// +// SgxStatus impls +// + +impl SgxStatus { + pub fn err(&self) -> Option<&SgxError> { + match self { + SgxStatus::Error(error) => Some(error), + _ => None, + } + } +} + +impl fmt::Display for SgxStatus { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, fmt) + } +} + +impl From for SgxStatus { + fn from(status: sgx_status_t) -> Self { + if status == 0 { + SgxStatus::Success + } else if let Some(sgx_error_code) = SgxError::from_u32(status) { + SgxStatus::Error(sgx_error_code) + } else { + SgxStatus::Unknown(status) + } + } +} + +impl From for SgxStatus { + fn from(sgx_error: SgxError) -> Self { + SgxStatus::Error(sgx_error) + } +} + + +impl From for sgx_status_t { + fn from(sgx_status: SgxStatus) -> Self { + match sgx_status { + SgxStatus::Success => SGX_SUCCESS, + SgxStatus::Error(sgx_error) => sgx_error.into(), + SgxStatus::Unknown(sgx_status) => sgx_status, + } + } +} + +// +// SgxError impls +// + +impl fmt::Display for SgxError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, fmt) + } +} + +impl From for sgx_status_t { + fn from(sgx_error: SgxError) -> Self { + sgx_error as sgx_status_t + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_sgx_quote_align() { + assert_eq!(std::mem::align_of::(), 1); + } +} diff --git a/service/kbupd/src/enclave/ffi/sgxsd.rs b/service/kbupd/src/enclave/ffi/sgxsd.rs new file mode 100644 index 0000000..413cb4d --- /dev/null +++ b/service/kbupd/src/enclave/ffi/sgxsd.rs @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fmt; +use std::mem; +use std::os::raw::*; + +use byteorder::{ByteOrder}; + +use crate::protobufs::kbupd::*; + +use super::ocalls; +use super::sgx::*; + +use super::bindgen_wrapper::{ + sgx_calc_quote_size, + sgx_get_quote, + sgx_init_quote, + sgx_target_info_t, + sgx_report_t, + sgx_spid_t, + sgx_status_t, + sgx_quote_t, + sgxsd_enclave_get_next_report, + sgxsd_enclave_negotiate_request, + sgxsd_enclave_node_init, + sgxsd_enclave_set_current_quote, + sgxsd_enclave_server_start, + sgxsd_enclave_server_call, + sgxsd_msg_tag_t, + sgxsd_msg_tag__bindgen_ty_1, + sgxsd_node_init_args_t, + sgxsd_request_negotiation_response_t, + sgxsd_server_init_args_t, + SGX_UNLINKABLE_SIGNATURE, +}; + +pub use super::bindgen_wrapper::{ + sgxsd_server_state_handle_t as SgxsdServerHandle, + sgxsd_server_handle_call_args_t as SgxsdServerCallArgs, + sgxsd_request_negotiation_request as SgxsdRequestNegotiationRequest, + sgxsd_request_negotiation_response as SgxsdRequestNegotiationResponse, + sgxsd_curve25519_public_key_t as SgxsdCurve25519PublicKey, + sgxsd_msg_header_t as SgxsdMessageHeader, + sgxsd_aes_gcm_iv_t as SgxsdAesGcmIv, + sgxsd_aes_gcm_mac_t as SgxsdAesGcmMac, + sgxsd_pending_request_id_t as SgxsdPendingRequestId, + KBUPD_REQUEST_TYPE_ANY, + KBUPD_REQUEST_TYPE_BACKUP, + KBUPD_REQUEST_TYPE_RESTORE, + KBUPD_REQUEST_TYPE_DELETE, +}; + +pub struct MessageReply { + pub iv: SgxsdAesGcmIv, + pub mac: SgxsdAesGcmMac, + pub data: Vec, +} + +pub struct MessageTag { + pub callback: Box) + Send>, +} + +pub struct SgxQuote { + pub gid: u32, + pub data: Vec, +} + +pub type SgxsdResult = Result; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum SgxsdErrorKind { + Returned, + Sgx, +} + +#[derive(Clone, Copy, failure::Fail)] +pub struct SgxsdError { + pub kind: SgxsdErrorKind, + pub status: SgxStatus, + pub name: &'static str, +} + +// +// MessageTag impls +// + +impl MessageTag { + fn into_tag(self) -> sgxsd_msg_tag_t { + sgxsd_msg_tag_t { + __bindgen_anon_1: sgxsd_msg_tag__bindgen_ty_1 { + p_tag: Box::into_raw(Box::new(self)) as *mut c_void + } + } + } + pub unsafe fn from_tag(raw_tag: sgxsd_msg_tag_t) -> Option { + let p_tag = raw_tag.__bindgen_anon_1.p_tag; + if !p_tag.is_null() { + Some(*Box::from_raw(p_tag as *mut MessageTag)) + } else { + None + } + } +} + +// +// SgxsdErrorKind impls +// + +impl SgxsdErrorKind { + fn as_str(&self) -> &'static str { + match self { + SgxsdErrorKind::Returned => "returned error", + SgxsdErrorKind::Sgx => "call failed", + } + } +} + +// +// SgxsdError impls +// + +impl fmt::Debug for SgxsdError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + if let Some(sgx_error) = self.status.err() { + write!(fmt, "{} {}: {}", self.name, self.kind.as_str(), sgx_error) + } else { + write!(fmt, "{} {}", self.name, self.kind.as_str()) + } + } +} +impl fmt::Display for SgxsdError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, fmt) + } +} + +// +// SgxQuote impls +// + +impl SgxQuote { + pub const SIZE: usize = mem::size_of::() - 4; +} + +// +// plain functions +// + +pub fn sgxsd_res(ecall: F, name: &'static str) -> SgxsdResult<()> +where F: FnOnce(&mut sgx_status_t) -> sgx_status_t { + let mut res: sgx_status_t = SgxStatus::Success.into(); + match SgxStatus::from(ecall(&mut res)) { + SgxStatus::Success => { + match SgxStatus::from(res) { + SgxStatus::Success => Ok(()), + status => Err(SgxsdError { kind: SgxsdErrorKind::Returned, status, name }), + } + } + status => Err(SgxsdError { kind: SgxsdErrorKind::Sgx, status, name }), + } +} + +pub fn sgxsd_node_init(enclave_id: SgxEnclaveId, pending_requests_table_order: u8) -> SgxsdResult<()> { + let args = sgxsd_node_init_args_t { pending_requests_table_order }; + sgxsd_res(|res| unsafe { sgxsd_enclave_node_init(enclave_id, res, &args) }, "sgxsd_enclave_node_init") +} + +pub fn sgxsd_negotiate_request(enclave_id: SgxEnclaveId, request: &SgxsdRequestNegotiationRequest) + -> SgxsdResult { + let mut response: sgxsd_request_negotiation_response_t = Default::default(); + sgxsd_res(|res| unsafe { sgxsd_enclave_negotiate_request(enclave_id, res, request, &mut response) }, + "sgxsd_enclave_negotiate_request") + .map(|_| response) +} + +pub fn sgxsd_get_next_quote(enclave_id: SgxEnclaveId, spid: &[u8; 16], sig_rl: &[u8]) -> SgxsdResult { + let mut quote_size: u32 = 0; + let (p_sig_rl, sig_rl_size) = get_sig_rl_ptr(sig_rl); + sgxsd_res(|_| unsafe { sgx_calc_quote_size(p_sig_rl, sig_rl_size, &mut quote_size) }, "sgx_calc_quote_size") + .and_then(|_| { + let mut quote: Vec = vec![0; quote_size as usize]; + sgxsd_get_next_quote_sized(enclave_id, spid, sig_rl, &mut quote[..]) + .map(|gid| SgxQuote { + gid, + data: quote, + }) + }) +} + +fn sgxsd_get_next_quote_sized(enclave_id: SgxEnclaveId, spid: &[u8; 16], sig_rl: &[u8], + p_quote: &mut [u8]) -> SgxsdResult { + // NB: sgx_init_quote expects qe_target_info to be zeroed (undocumented!) + let mut qe_target_info: sgx_target_info_t = Default::default(); + let mut gid: [u8; 4] = Default::default(); + sgxsd_res(|_| unsafe { sgx_init_quote(&mut qe_target_info, &mut gid) }, "sgx_init_quote") + .and_then(|_| { + let mut report: sgx_report_t = Default::default(); + sgxsd_res(|res| unsafe { sgxsd_enclave_get_next_report( + enclave_id, res, qe_target_info, &mut report + )}, "sgxsd_enclave_get_next_quote")?; + Ok(report) + }) + .and_then(|report| { + let quote_size = p_quote.len() as u32; + let spid_struct = sgx_spid_t { id: *spid }; + let p_nonce = std::ptr::null_mut(); + let (p_sig_rl, sig_rl_size) = get_sig_rl_ptr(sig_rl); + let p_qe_report = std::ptr::null_mut(); + sgxsd_res(|_| unsafe { sgx_get_quote( + &report, SGX_UNLINKABLE_SIGNATURE, + &spid_struct, p_nonce, p_sig_rl, sig_rl_size, p_qe_report, p_quote.as_mut_ptr() as *mut sgx_quote_t, quote_size + )}, "sgx_get_quote") + }) + .map(|()| byteorder::LittleEndian::read_u32(&gid)) +} + +pub fn sgxsd_set_current_quote(enclave_id: SgxEnclaveId) -> SgxsdResult<()> { + sgxsd_res(|res| unsafe { sgxsd_enclave_set_current_quote(enclave_id, res) }, + "sgxsd_set_current_quote") +} + +pub fn sgxsd_server_start(enclave_id: SgxEnclaveId, server_handle: SgxsdServerHandle) -> SgxsdResult> { + let args = sgxsd_server_init_args_t {}; + sgxsd_res(|res| unsafe { sgxsd_enclave_server_start(enclave_id, res, &args, server_handle) }, + "sgxsd_enclave_server_start")?; + Ok(ocalls::take_enclave_messages()) +} +pub fn sgxsd_server_call(enclave_id: SgxEnclaveId, + args: SgxsdServerCallArgs, + msg_header: &SgxsdMessageHeader, + msg_data: &[u8], + reply_fun: impl FnOnce(SgxsdResult) + Send + 'static, + server_handle: SgxsdServerHandle) + -> SgxsdResult> +{ + let tag = MessageTag { + callback: Box::new(reply_fun), + }.into_tag(); + sgxsd_res(|res| unsafe { sgxsd_enclave_server_call( + enclave_id, res, &args, msg_header, + msg_data.as_ptr() as *mut u8, msg_data.len(), + tag, server_handle) + }, "sgxsd_enclave_server_call") + .map(|()| ocalls::take_enclave_messages()) + .map_err(|error: SgxsdError| { + if let Some(message_tag) = unsafe { MessageTag::from_tag(tag) } { + (message_tag.callback)(Err(error.clone())); + } + error + }) +} diff --git a/service/kbupd/src/enclave/handshake_manager.rs b/service/kbupd/src/enclave/handshake_manager.rs new file mode 100644 index 0000000..436dacc --- /dev/null +++ b/service/kbupd/src/enclave/handshake_manager.rs @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::time::{Duration, Instant}; + +use futures::prelude::*; +use try_future::{TryFuture}; +use tokio::timer; + +use crate::*; +use crate::intel_client::*; +use crate::metrics::*; + +const REFRESH_INTERVAL: Duration = Duration::from_secs(60); + +pub struct HandshakeManager { + enclave_tx: EnclaveManagerSender, + intel_client: IntelClient, + accept_group_out_of_date: bool, +} + +#[derive(Debug, failure::Fail)] +enum RefreshAllError { + #[fail(display = "stale revocation list")] + StaleRevocationList, +} + +lazy_static::lazy_static! { + static ref GET_SIGNED_QUOTE_OK_METER: Meter = METRICS.metric(&metric_name!("get_signed_quote", "ok")); + static ref GET_SIGNED_QUOTE_ERROR_METER: Meter = METRICS.metric(&metric_name!("get_signed_quote", "error")); +} + +impl HandshakeManager { + pub fn new(enclave_tx: EnclaveManagerSender, + intel_client: IntelClient, + accept_group_out_of_date: bool) + -> Self { + Self { + enclave_tx, + intel_client, + accept_group_out_of_date + } + } + + pub fn fetch_all(self) -> impl Future + Send + 'static { + let fetched_sig_rl = self.fetch_sig_rl(); + let enclave_tx = self.enclave_tx.clone(); + let quotes_vec = fetched_sig_rl.and_then(move |()| { + enclave_tx.call(|enclave_manager: &mut EnclaveManager, reply_tx| { + enclave_manager.get_next_quotes(reply_tx) + }) + }); + + let quotes = quotes_vec.map(|quotes: Vec<(String, SgxQuote)>| { + futures::stream::iter_ok(quotes) + }).flatten_stream(); + + let state = quotes.fold(self, |state: Self, (enclave_name, sgx_quote): (String, SgxQuote)| { + let state = state.fetch(enclave_name, sgx_quote); + state.map_err(|(_, error): (Self, GetQuoteSignatureError)| error) + }); + + state.from_err() + } + + fn fetch(self, enclave_name: String, quote: SgxQuote) + -> impl Future + Send + 'static { + let signed_quote = self.intel_client.get_quote_signature(quote.data, self.accept_group_out_of_date); + + let state = signed_quote.then(move |result: Result| { + match result { + Ok(signed_quote) => { + GET_SIGNED_QUOTE_OK_METER.mark(); + let _ignore = self.enclave_tx.cast(move |enclave_manager: &mut EnclaveManager| { + enclave_manager.set_current_quote(enclave_name, signed_quote) + }); + Ok(self) + } + Err(get_signed_quote_error) => { + GET_SIGNED_QUOTE_ERROR_METER.mark(); + Err((self, get_signed_quote_error)) + } + } + }); + + state + } + + fn fetch_sig_rl(&self) -> impl Future + Send + 'static { + let gid = self.enclave_tx.call(|enclave_manager: &mut EnclaveManager, reply_tx| { + enclave_manager.get_sgx_gid(reply_tx) + }); + + let intel_client = self.intel_client.clone(); + let sig_rl = gid.and_then(move |gid: u32| { + intel_client.get_signature_revocation_list(gid) + }); + + let enclave_tx = self.enclave_tx.clone(); + let set_sig_rl = sig_rl.map(move |sig_rl: SignatureRevocationList| { + let _ignore = enclave_tx.cast(|enclave_manager: &mut EnclaveManager| { + enclave_manager.set_signature_revocation_list(sig_rl) + }); + }); + + set_sig_rl + } + + pub fn into_future(self) -> impl Future + Send { + let interval_timer_stream = timer::Interval::new_interval(REFRESH_INTERVAL).map_err(|error| { + error!("tokio timer error: {}", error); + }); + + let interval_timer = interval_timer_stream.fold(self, |state: Self, _now: Instant| { + let refresh_all_result = state.refresh_all(); + + let refresh_all_result = refresh_all_result.or_else(|(state, error): (Self, RefreshAllError)| { + match error { + RefreshAllError::StaleRevocationList => { + info!("fetching new signature revocation list from IAS"); + let sig_rl = state.fetch_sig_rl(); + let fetch_result = sig_rl.then(move |sig_rl_result: Result<(), failure::Error>| { + match sig_rl_result { + Ok(()) => state.refresh_all().map_err(|(state, error)| (state, error.into())).into(), + Err(error) => TryFuture::from_error((state, error.context("error fetching revocation list from IAS").into())), + } + }); + fetch_result + } + } + }); + + let refresh_all_result = refresh_all_result.or_else(|(state, error): (Self, failure::Error)| -> Result { + warn!("error refreshing all quotes: {:?}", error); + Ok(state) + }); + + refresh_all_result + }); + + interval_timer.map(|_state: Self| { + error!("tokio timer terminated"); + }) + } + + fn refresh_all(self) -> impl Future { + let quotes_vec = self.enclave_tx.call(|enclave_manager: &mut EnclaveManager, reply_tx| { + enclave_manager.get_next_quotes(reply_tx) + }); + + let quotes = quotes_vec.then(|quotes_result: Result, failure::Error>| { + match quotes_result { + Ok(quotes) => Ok(futures::stream::iter_ok(quotes)), + Err(error) => { + warn!("error retrieving quotes: {:?}", error); + Ok(futures::stream::iter_ok(vec![])) + } + } + }).flatten_stream(); + + quotes.fold(self, |state: Self, (enclave_name, sgx_quote): (String, SgxQuote)| { + state.fetch(enclave_name, sgx_quote).then(|fetch_result: Result| { + fetch_result.or_else(|(state, error)| { + match error { + GetQuoteSignatureError::QuoteVerificationError(QuoteVerificationError::StaleRevocationList) => { + Err((state, RefreshAllError::StaleRevocationList)) + } + error => { + warn!("error fetching quote from IAS: {:?}", error); + Ok(state) + } + } + }) + }) + }) + } +} diff --git a/service/kbupd/src/enclave/mod.rs b/service/kbupd/src/enclave/mod.rs new file mode 100644 index 0000000..147a93b --- /dev/null +++ b/service/kbupd/src/enclave/mod.rs @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod attestation_manager; +pub mod enclave; +pub mod enclave_manager; +pub mod error; +mod ffi; +pub mod handshake_manager; +pub mod revocation_list_refresh; +pub mod status_refresh; +pub mod timer_tick; diff --git a/service/kbupd/src/enclave/revocation_list_refresh.rs b/service/kbupd/src/enclave/revocation_list_refresh.rs new file mode 100644 index 0000000..297cbb9 --- /dev/null +++ b/service/kbupd/src/enclave/revocation_list_refresh.rs @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::time::*; + +use futures::prelude::*; +use tokio::timer; + +use crate::*; +use crate::intel_client::*; + +pub struct RevocationListRefreshTask { + interval: Duration, + intel_client: IntelClient, + enclave_manager_tx: EnclaveManagerSender, +} + +impl RevocationListRefreshTask { + pub fn new(interval: Duration, + intel_client: IntelClient, + enclave_manager_tx: EnclaveManagerSender) + -> Self + { + Self { + interval, + intel_client, + enclave_manager_tx, + } + } + + fn refresh_revocation_list(self) -> impl Future { + let gid = self.enclave_manager_tx.call(|enclave_manager: &mut EnclaveManager, reply_tx| { + enclave_manager.get_sgx_gid(reply_tx) + }); + + let intel_client = self.intel_client.clone(); + let revocation_list = gid.and_then(move |gid: u32| { + info!("fetching signature revocation list for gid: {:08x}", gid); + intel_client.get_signature_revocation_list(gid) + }); + + let sent_revocation_list = revocation_list.then(|revocation_list_result: Result| { + match revocation_list_result { + Ok(revocation_list) => { + self.enclave_manager_tx.cast(move |enclave_manager: &mut EnclaveManager| { + enclave_manager.set_signature_revocation_list(revocation_list) + })?; + } + Err(error) => { + warn!("error fetching revocation list from IAS: {:?}", error); + } + } + Ok(self) + }); + + sent_revocation_list + } + + pub fn into_future(self) -> impl Future { + let interval_timer_stream = timer::Interval::new_interval(self.interval).map_err(|error: timer::Error| { + error!("tokio timer error: {}", error); + }); + + let interval_timer = interval_timer_stream.fold(self, |state: Self, _now: Instant| { + state.refresh_revocation_list() + }); + + interval_timer.map(|_state: Self| { + error!("tokio timer terminated"); + }) + } +} diff --git a/service/kbupd/src/enclave/status_refresh.rs b/service/kbupd/src/enclave/status_refresh.rs new file mode 100644 index 0000000..34dfd82 --- /dev/null +++ b/service/kbupd/src/enclave/status_refresh.rs @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::time::*; + +use futures::prelude::*; +use tokio::timer; + +use crate::*; + +pub struct EnclaveStatusRefreshTask { + interval: Duration, + enclave_manager_tx: EnclaveManagerSender, +} + +impl EnclaveStatusRefreshTask { + pub fn new(interval: Duration, + enclave_manager_tx: EnclaveManagerSender) + -> Self + { + Self { + interval, + enclave_manager_tx, + } + } + + fn refresh_status(self) -> Result { + self.enclave_manager_tx.cast(move |enclave_manager: &mut EnclaveManager| { + enclave_manager.refresh_status(false) + })?; + Ok(self) + } + + pub fn into_future(self) -> impl Future { + let interval_timer_stream = timer::Interval::new_interval(self.interval).map_err(|error: timer::Error| { + error!("tokio timer error: {}", error); + }); + + let interval_timer = interval_timer_stream.fold(self, |state: Self, _now: Instant| { + state.refresh_status() + }); + + interval_timer.map(|_state: Self| { + error!("tokio timer terminated"); + }) + } +} diff --git a/service/kbupd/src/enclave/timer_tick.rs b/service/kbupd/src/enclave/timer_tick.rs new file mode 100644 index 0000000..62a73e0 --- /dev/null +++ b/service/kbupd/src/enclave/timer_tick.rs @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::time::*; + +use futures::prelude::*; +use tokio::timer; + +use crate::*; +use crate::protobufs::kbupd::*; + +pub struct EnclaveTimerTickTask { + interval: Duration, + enclave_name: String, + enclave_manager_tx: EnclaveManagerSender, +} + +impl EnclaveTimerTickTask { + pub fn new(interval: Duration, + enclave_name: String, + enclave_manager_tx: EnclaveManagerSender) + -> Self + { + Self { + interval, + enclave_name, + enclave_manager_tx, + } + } + + fn tick(self) -> Result { + let enclave_name = self.enclave_name.clone(); + let message = UntrustedMessage { + inner: Some(untrusted_message::Inner::TimerTickSignal(TimerTickSignal { + now_secs: SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs(), + })), + }; + self.enclave_manager_tx.cast(move |enclave_manager: &mut EnclaveManager| { + enclave_manager.untrusted_message(enclave_name, message) + })?; + Ok(self) + } + + pub fn into_future(self) -> impl Future { + let interval_timer_stream = timer::Interval::new_interval(self.interval).map_err(|error: timer::Error| { + error!("tokio timer error: {}", error); + }); + + let interval_timer = interval_timer_stream.fold(self, |state: Self, _now: Instant| { + state.tick() + }); + + interval_timer.map(|_state: Self| { + error!("tokio timer terminated"); + }) + } +} diff --git a/service/kbupd/src/frontend/config.rs b/service/kbupd/src/frontend/config.rs new file mode 100644 index 0000000..052a63f --- /dev/null +++ b/service/kbupd/src/frontend/config.rs @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(non_snake_case)] + +use kbupd_config::frontend::*; + +use crate::limits::leaky_bucket::{LeakyBucketParameters}; +use crate::protobufs::kbupd::{BackupId, PartitionKeyRangePb}; + +// +// FrontendRateLimitConfig impls +// + +impl From for LeakyBucketParameters { + fn from(config: FrontendRateLimitConfig) -> Self { + Self { + size: config.bucketSize, + leak_rate: config.leakRatePerMinute / 60.0, + } + } +} + +// +// PartitionKeyRangePb impls +// + +impl From<&FrontendPartitionRangeConfig> for PartitionKeyRangePb { + fn from(range: &FrontendPartitionRangeConfig) -> Self { + Self { + first: BackupId { id: range.firstBackupId.to_vec() }, + last: BackupId { id: range.lastBackupId.to_vec() }, + } + } +} diff --git a/service/kbupd/src/frontend/mod.rs b/service/kbupd/src/frontend/mod.rs new file mode 100644 index 0000000..3664559 --- /dev/null +++ b/service/kbupd/src/frontend/mod.rs @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod config; + +use std::path::{Path}; +use std::sync::{Arc}; +use std::time::{Duration}; +use std::thread; + +use failure::{format_err, ResultExt}; +use futures::future; +use futures::prelude::*; +use hyper::client::connect::{HttpConnector}; +use kbupd_config::{FrontendConfig}; +use kbupd_config::metrics::*; +use kbuptlsd::prelude::*; +use nix::sys::signal; +use nix::sys::signal::Signal::*; + +use crate::*; +use crate::api::auth::signal_user::{SignalUserAuthenticator}; +use crate::api::listener::{ApiListener}; +use crate::api::service::*; +use crate::backup::manager::*; +use crate::backup::request_manager::*; +use crate::enclave::attestation_manager::{AttestationManager}; +use crate::enclave::timer_tick::{EnclaveTimerTickTask}; +use crate::enclave::status_refresh::{EnclaveStatusRefreshTask}; +use crate::limits::rate_limiter::*; +use crate::metrics::{JsonReporter, METRICS, PeriodicReporter}; +use crate::peer::discovery::*; +use crate::peer::manager::*; +use crate::protobufs::kbupd::*; +use crate::tls::*; + +const REPLICA_TIMEOUT_TICKS: u32 = 5; + +const REQUEST_QUOTE_INTERVAL: Duration = Duration::from_secs(600); + +const MIN_CONNECT_TIMEOUT: Duration = Duration::from_secs(15); +const MAX_CONNECT_TIMEOUT: Duration = Duration::from_secs(120); + +const DEFAULT_METRICS_INTERVAL: Duration = Duration::from_secs(60); +const ENCLAVE_STATUS_REFRESH_INTERVAL: Duration = Duration::from_secs(60); + +const REQUEST_CACHE_TTL: Duration = Duration::from_secs(600); + +const PENDING_REQUESTS_TABLE_ORDER: u8 = 15; + +pub struct FrontendService { + runtime: tokio::runtime::Runtime, + enclave_thread_joiner: Box, Error = Box> + Send>, +} + +#[derive(Clone)] +pub struct FrontendCommandLineConfig<'a> { + pub enclave_directory: &'a Path, + pub config_directory: &'a Path, + pub kbuptlsd_bin_path: &'a Path, + pub full_hostname: Option<&'a str>, +} + +impl FrontendService { + pub fn start(config: FrontendConfig, + cmdline_config: FrontendCommandLineConfig, + peer_tls_client_args: TlsClientProxyArguments) + -> Result + { + let mut runtime = tokio::runtime::Builder::new().build().context("error starting tokio runtime")?; + let mut executor = runtime.executor(); + + let enclave_manager_channel = EnclaveManagerChannel::new(); + let enclave_manager_tx = enclave_manager_channel.sender().clone(); + + let intel_client; + let handshake_manager; + if !config.attestation.disabled { + let intel_client_proxy = TlsClientProxySpawner::new(cmdline_config.kbuptlsd_bin_path.to_owned(), TlsClientProxyArguments::Config { + config_file: util::join_if_relative(cmdline_config.config_directory, &config.attestation.tlsConfigPath), + key_file: None, + }).context("error creating intel attestation tls proxy client")?; + let new_intel_client = IntelClient::new(&config.attestation.host, intel_client_proxy).context("error creating intel attestation client")?; + handshake_manager = Some(HandshakeManager::new(enclave_manager_tx.clone(), new_intel_client.clone(), config.attestation.acceptGroupOutOfDate)); + intel_client = Some(new_intel_client); + } else { + handshake_manager = None; + intel_client = None; + } + + let peer_tls_client = TlsClient::new(cmdline_config.kbuptlsd_bin_path.to_owned(), peer_tls_client_args).context("error creating peer tls client")?; + + let mut enclave_configs = Vec::new(); + for enclave_config in config.enclaves { + let mut partition_discoveries = Vec::new(); + for partition_config in &enclave_config.partitions { + let mut addresses = Vec::new(); + for replica in &partition_config.replicas { + addresses.push(replica.hostPort.clone()); + } + + let range = partition_config.range.as_ref().map(PartitionKeyRangePb::from); + partition_discoveries.push(PartitionPeerDiscovery::new(range, addresses, &peer_tls_client).discover()); + } + + let partitions = runtime.block_on(future::join_all(partition_discoveries)) + .map_err(|()| failure::err_msg("error during partition discovery"))?; + enclave_configs.push((enclave_config, partitions)); + } + + let (enclave_init_tx, enclave_init_rx) = std::sync::mpsc::channel::<()>(); + let (enclave_join_tx, enclave_join_rx) = futures::sync::oneshot::channel::(); + + let enclave_spid = config.attestation.spid; + let enclave_executor = runtime.executor(); + let enclave_directory = cmdline_config.enclave_directory.to_owned(); + let enclave_thread = thread::spawn(move || -> Result<(), failure::Error> { + let mut enclaves = Vec::with_capacity(enclave_configs.len()); + for (enclave_config, partitions) in enclave_configs { + let (peer_manager_tx, peer_manager_rx) = actor::channel(); + let (attestation_manager_tx, attestation_manager_rx) = actor::channel(); + + let enclave_name = enclave_config.name.to_string(); + let enclave_filename = format!("{}.so", &enclave_config.mrenclave); + let enclave_path = enclave_directory.join(&enclave_filename); + + let replica_timeout_ticks = REPLICA_TIMEOUT_TICKS; + let replica_timeout = Duration::from_millis(enclave_config.electionTimeoutMs) * 4; + let pending_request_ttl = Duration::from_millis(enclave_config.pendingRequestTtlMs); + let timer_tick_interval = replica_timeout / replica_timeout_ticks; + + let enclave_frontend_config = EnclaveFrontendConfig { + replica_timeout_ticks, + request_quote_ticks: util::duration::as_ticks(REQUEST_QUOTE_INTERVAL, timer_tick_interval), + min_connect_timeout_ticks: util::duration::as_ticks(MIN_CONNECT_TIMEOUT, timer_tick_interval), + max_connect_timeout_ticks: util::duration::as_ticks(MAX_CONNECT_TIMEOUT, timer_tick_interval), + pending_request_ttl: util::duration::as_ticks(pending_request_ttl, timer_tick_interval), + pending_request_count: enclave_config.pendingRequestCount, + }; + + let mut partition_configs = Vec::new(); + for (partition_config, peer_discovery) in partitions { + partition_configs.push(partition_config); + enclave_executor.spawn(peer_discovery.finish(peer_manager_tx.clone())); + } + + info!("starting enclave {} with mrenclave {} with timer tick interval {}ms and {:#?}", + &enclave_name, &enclave_config.mrenclave, timer_tick_interval.as_millis(), + &enclave_frontend_config); + + let start_frontend_request = StartFrontendRequest { + config: enclave_frontend_config, + partitions: partition_configs, + }; + + let mut enclave = (Enclave::new(enclave_name.clone(), &enclave_path.to_string_lossy(), enclave_config.debug, enclave_spid, peer_manager_tx.clone(), attestation_manager_tx.clone()) + .with_context(|_| format_err!("error creating enclave {}", enclave_name)))?; + let node_id = enclave.start_frontend(start_frontend_request, PENDING_REQUESTS_TABLE_ORDER) + .with_context(|_| format_err!("error starting frontend in enclave {}", enclave_name))?; + + let peer_manager = PeerManager::new(peer_manager_tx, enclave_config.name.to_string(), enclave_manager_channel.sender().clone(), node_id, peer_tls_client.clone()); + let timer_tick_task = EnclaveTimerTickTask::new(timer_tick_interval, enclave_name, enclave_manager_channel.sender().clone()); + + let attestation_manager = AttestationManager::new(enclave_manager_channel.sender().clone(), intel_client.clone()); + + enclave_executor.spawn(peer_manager_rx.enter_loop(peer_manager)); + enclave_executor.spawn(attestation_manager_rx.enter_loop(attestation_manager)); + enclave_executor.spawn(timer_tick_task.into_future()); + + enclaves.push(enclave); + } + + enclave_init_tx.send(())?; + + let mut enclave_manager = EnclaveManager::new(enclave_manager_channel, enclaves); + match enclave_manager.run() { + Ok(()) => info!("enclave manager stopped upon user request"), + Err(error) => { + error!("fatal enclave error: {}", error); + return Err(error.into()); + } + } + + drop(enclave_join_tx); + Ok(()) + }); + + match enclave_init_rx.recv() { + Ok(()) => (), + Err(_) => { + return Err(enclave_thread.join().unwrap().unwrap_err()); + } + } + + let handshake_manager = if let Some(handshake_manager) = handshake_manager { + Some(runtime.block_on(handshake_manager.fetch_all()).context("error fetching quotes from IAS")?) + } else { + None + }; + + let signal_user_authenticator = Arc::new(SignalUserAuthenticator::new(&config.api.userAuthenticationTokenSharedSecret)); + + let api_rate_limiters = SignalApiRateLimiters { + token: actor::spawn(RateLimiter::new("token", config.api.limits.token.into()), &mut executor)?, + attestation: actor::spawn(RateLimiter::new("attestation", config.api.limits.attestation.into()), &mut executor)?, + backup: actor::spawn(RateLimiter::new("backup", config.api.limits.backup.into()), &mut executor)?, + }; + + let (backup_request_manager_tx, backup_request_manager_rx) = actor::channel(); + let backup_request_manager = BackupRequestManager::new(REQUEST_CACHE_TTL); + + let status_refresh_task = EnclaveStatusRefreshTask::new(ENCLAVE_STATUS_REFRESH_INTERVAL, enclave_manager_tx.clone()); + let backup_id_key = ring::hmac::SigningKey::new(&ring::digest::SHA256, &config.api.backupIdSecret); + let backup_manager = SignalBackupManager::new(enclave_manager_tx.clone(), backup_id_key, backup_request_manager_tx); + let api_service = SignalApiService::new(signal_user_authenticator, backup_manager, api_rate_limiters); + let api_listener = ApiListener::new(&config.api.listenHostPort, api_service).context("error starting api listener")?; + let control_listener = ControlListener::new(config.control.listenHostPort, enclave_manager_tx).context("error starting control listener")?; + + runtime.spawn(api_listener.into_future()); + runtime.spawn(control_listener.into_future()); + runtime.spawn(status_refresh_task.into_future()); + runtime.spawn(backup_request_manager.enter_loop(backup_request_manager_rx).map(drop)); + if let Some(handshake_manager) = handshake_manager { + runtime.spawn(handshake_manager.into_future()); + } + + if let Some(metrics_config) = config.metrics { + for reporter_config in metrics_config.reporters { + let MetricsReporterConfig::Json(json_reporter_config) = reporter_config; + + let mut reporter_http_connector = HttpConnector::new(1); + reporter_http_connector.enforce_http(false); + + let reporter_tls_proxy = TlsClientProxySpawner::new(cmdline_config.kbuptlsd_bin_path.to_owned(), TlsClientProxyArguments::NoConfig { + ca: TlsClientProxyCaArgument::System, + key_file: None, + hostname: TlsClientProxyHostnameArgument::Hostname(json_reporter_config.hostname.to_string()), + }).context("error creating metrics json reporter tls proxy client")?; + + let reporter_tls_connector = TlsProxyConnector::new(Arc::new(reporter_tls_proxy), reporter_http_connector); + + let reporter_interval = json_reporter_config.intervalSeconds.map(Duration::from_secs) + .unwrap_or(DEFAULT_METRICS_INTERVAL); + let json_reporter = JsonReporter::new(&json_reporter_config.token, &json_reporter_config.hostname, cmdline_config.full_hostname, reporter_tls_connector).context("error creating metrics json reporter")?; + let periodic_reporter = PeriodicReporter::new(json_reporter, METRICS.clone(), reporter_interval); + + periodic_reporter.start(); + } + } + + unix_signal::ignore_signal(SIGPIPE).context("error setting sigaction")?; + unix_signal::ignore_signal(SIGCHLD).context("error setting sigaction")?; + + let unix_signals = unix_signal::handle_signals(vec![ + SIGHUP, + SIGUSR1, + SIGUSR2, + ]); + let unix_signals = runtime.block_on(unix_signals)?; + let handled_unix_signals = unix_signals.for_each(|signum: signal::Signal| { + info!("received unix signal {}", signum); + Ok(()) + }); + let unix_signal_task = handled_unix_signals.map_err(|error: std::io::Error| { + error!("error in unix signal handler: {}", error); + }); + runtime.spawn(unix_signal_task); + + let enclave_thread_joiner = Box::new(enclave_join_rx.then(move |_| enclave_thread.join())); + Ok(Self { runtime, enclave_thread_joiner }) + } + + pub fn join(mut self) { + match self.runtime.block_on(self.enclave_thread_joiner) { + Ok(Ok(())) => info!("enclave shutdown"), + Ok(Err(enclave_error)) => error!("enclave error: {}", enclave_error), + Err(_join_error) => error!("enclave thread died"), + } + drop(self.runtime); + } +} diff --git a/service/kbupd/src/intel_client.rs b/service/kbupd/src/intel_client.rs new file mode 100644 index 0000000..9c1245a --- /dev/null +++ b/service/kbupd/src/intel_client.rs @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fmt; +use std::sync::{Arc}; + +use futures::prelude::*; +use http::{self, Uri, HeaderMap}; +use http::header::{HeaderValue}; +use hyper::{Body, Chunk, Client, Method, Response, Request}; +use hyper::client::{HttpConnector}; +use kbupd_util::base64; +use kbuptlsd::prelude::*; +use serde_derive::{Deserialize, Serialize}; +use serde_json; + +use crate::*; +use crate::protobufs::kbupd::{IasReport}; +use crate::util; + +#[derive(Clone)] +pub struct IntelClient { + host: Uri, + client: Client, Body>, +} + +#[derive(Debug, failure::Fail)] +pub enum GetQuoteSignatureError { + #[fail(display = "error fetching signed quote: {:?}", _0)] + FetchError(#[cause] failure::Error), + #[fail(display = "quote verification error: {:?}", _0)] + QuoteVerificationError(#[cause] QuoteVerificationError), +} + +#[derive(Clone, Default)] +pub struct SignatureRevocationList(Vec); + +impl IntelClient { + pub fn new(host: &str, tls_proxy: TlsClientProxySpawner) -> Result { + let mut http_connector = HttpConnector::new(1); + http_connector.enforce_http(false); + + let tls_connector = TlsProxyConnector::new(Arc::new(tls_proxy), http_connector); + + let host = host.parse()?; + let client = Client::builder().build(tls_connector); + Ok(Self { + host, + client, + }) + } + + pub fn get_signature_revocation_list(&self, gid: u32) -> impl Future { + let mut uri_parts = self.host.clone().into_parts(); + let uri_path_and_query = try_future!(format!("/attestation/sgx/v3/sigrl/{:08x}", gid).parse::()); + uri_parts.path_and_query = Some(uri_path_and_query); + let uri = try_future!(Uri::from_parts(uri_parts)); + + let response = self.client.get(uri); + let response_data = response.and_then(|response: Response| { + response.into_body().concat2() + }).from_err(); + + let decoded_response = response_data.and_then(|data: Chunk| { + base64::decode(&data).map(SignatureRevocationList).into_future().from_err() + }); + + decoded_response.into() + } + + fn fetch_quote_signature(&self, quote: &[u8]) -> impl Future { + let mut uri_parts = self.host.clone().into_parts(); + let uri_path_and_query = try_future!("/attestation/sgx/v3/report".parse::()); + uri_parts.path_and_query = Some(uri_path_and_query); + let uri = try_future!(Uri::from_parts(uri_parts)); + + let request = QuoteSignatureRequest { + isvEnclaveQuote: quote, + }; + let encoded_request = try_future!(serde_json::to_vec(&request)); + let mut hyper_request = Request::new(Body::from(encoded_request)); + + *hyper_request.method_mut() = Method::POST; + *hyper_request.uri_mut() = uri; + hyper_request.headers_mut().insert("Content-Type", HeaderValue::from_static("application/json")); + + let response = self.client.request(hyper_request); + let full_response = response.and_then(move |response: Response| { + let (response_parts, response_body) = response.into_parts(); + + let response_data = response_body.concat2(); + + response_data.map(|response_data| (response_parts, response_data)) + }); + full_response.from_err().into() + } + + pub fn get_quote_signature(&self, quote: Vec, accept_group_out_of_date: bool) + -> impl Future { + let response = self.fetch_quote_signature("e); + let signed_quote = response.then(move |response_result: Result<(http::response::Parts, Chunk), failure::Error>| { + let (response_parts, response_data) = response_result.map_err(GetQuoteSignatureError::FetchError)?; + + let signed_quote_result = validate_quote_signature(response_parts, response_data, quote, accept_group_out_of_date); + signed_quote_result.map_err(GetQuoteSignatureError::QuoteVerificationError) + }); + + signed_quote + } +} + +fn validate_quote_signature(response_parts: http::response::Parts, + response_body_data: Chunk, + quote: Vec, + accept_group_out_of_date: bool) + -> Result { + if !response_parts.status.is_success() { + let response_body_string = String::from_utf8_lossy(&response_body_data).to_string(); + return Err(QuoteVerificationError::HttpError(response_parts.status, response_parts, response_body_string)); + } + + let base64_signature = get_header_str(&response_parts.headers, "X-IASReport-Signature")?; + let pem_certificates = get_header_str(&response_parts.headers, "X-IASReport-Signing-Certificate")?; + + let signature = base64::decode(base64_signature.as_bytes()) + .map_err(|_| QuoteVerificationError::InvalidSignature(base64_signature.to_string()))?; + + let certificates = util::pem::decode(&util::percent::decode(pem_certificates.as_bytes())); + + if certificates.is_empty() { + return Err(QuoteVerificationError::InvalidCertificates(pem_certificates.to_string())); + } + + let body = response_body_data.to_vec(); + + let parsed_body: QuoteSignatureResponseBody = + serde_json::from_slice(&body) + .map_err(|parse_error| QuoteVerificationError::InvalidJson(parse_error.into()))?; + + if parsed_body.version != 3 { + return Err(QuoteVerificationError::WrongVersion(parsed_body.version)); + } + + if Some(&parsed_body.isvEnclaveQuoteBody[..]) != quote.get(..SgxQuote::SIZE) { + return Err(QuoteVerificationError::WrongQuote); + } + + match parsed_body.isvEnclaveQuoteStatus.as_str() { + "OK" => { + } + "GROUP_OUT_OF_DATE" | "CONFIGURATION_NEEDED" => { + if !accept_group_out_of_date { + return Err(QuoteVerificationError::GroupOutOfDate(parsed_body.isvEnclaveQuoteStatus.clone(), parsed_body)); + } + } + "GROUP_REVOKED" => { + return Err(QuoteVerificationError::GroupOutOfDate(parsed_body.isvEnclaveQuoteStatus.clone(), parsed_body)); + } + "SIGRL_VERSION_MISMATCH" => { + return Err(QuoteVerificationError::StaleRevocationList); + } + _ => { + return Err(QuoteVerificationError::AttestationError(parsed_body.isvEnclaveQuoteStatus)); + } + } + + // XXX validate timestamp + + Ok(SignedQuote { + quote, + report: IasReport { + body, + signature, + certificates, + }, + }) +} + +fn get_header_str<'a>(headers: &'a HeaderMap, name: &'static str) -> Result<&'a str, QuoteVerificationError> { + if let Some(header) = headers.get(name) { + match header.to_str() { + Ok(header) => Ok(header), + Err(_) => Err(QuoteVerificationError::InvalidHeaderValue(name, header.clone())), + } + } else { + Err(QuoteVerificationError::MissingHeader(name)) + } +} + +impl std::ops::Deref for SignatureRevocationList { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } +} + +#[derive(failure::Fail)] +pub enum QuoteVerificationError { + #[fail(display = "attestation http error: {}", _0)] + HttpError(http::status::StatusCode, http::response::Parts, String), + #[fail(display = "missing attestation http header {}", _0)] + MissingHeader(&'static str), + #[fail(display = "invalid attestation http header value for {}: {:?}", _0, _1)] + InvalidHeaderValue(&'static str, HeaderValue), + #[fail(display = "invalid attestation signature: {}", _0)] + InvalidSignature(String), + #[fail(display = "invalid attestation certificates: {}", _0)] + InvalidCertificates(String), + #[fail(display = "invalid attestation report json: {}", _0)] + InvalidJson(#[cause] failure::Error), + #[fail(display = "invalid attestation report version: {}", _0)] + WrongVersion(u64), + #[fail(display = "wrong attestation report quote")] + WrongQuote, + #[fail(display = "stale attestation revocation list")] + StaleRevocationList, + #[fail(display = "attestation group out of date: {}", _0)] + GroupOutOfDate(String, QuoteSignatureResponseBody), + #[fail(display = "attestation error: {}", _0)] + AttestationError(String) +} + +impl fmt::Debug for QuoteVerificationError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +#[derive(Debug)] +pub struct SignedQuote { + pub quote: Vec, + pub report: IasReport, +} + +#[allow(non_snake_case)] +#[derive(Serialize)] +pub struct QuoteSignatureRequest<'a> { + #[serde(with = "base64")] + pub isvEnclaveQuote: &'a [u8], +} + +#[allow(non_snake_case)] +#[derive(Deserialize, Debug)] +pub struct QuoteSignatureResponseBody { + pub isvEnclaveQuoteStatus: String, + + #[serde(with = "base64")] + pub isvEnclaveQuoteBody: Vec, + + pub version: u64, + + pub timestamp: String, + + pub platformInfoBlob: Option, +} diff --git a/service/kbupd/src/kbupd.proto b/service/kbupd/src/kbupd.proto new file mode 100644 index 0000000..06b016b --- /dev/null +++ b/service/kbupd/src/kbupd.proto @@ -0,0 +1,648 @@ +syntax = "proto2"; + +package protobufs.kbupd; + +import "kbupd_client.proto"; + +// +// shared types +// + +message ServiceId { + required bytes id = 1; +} + +message BackupId { + required bytes id = 1; +} + +message PartitionKeyRangePB { + required BackupId first = 1; + required BackupId last = 2; +} + +message IasReport { + required bytes body = 2; + required bytes signature = 3; + repeated bytes certificates = 4; +} + +message AttestationParameters { + required uint64 unix_timestamp_seconds = 1; +} + +enum XferControlCommand { + START = 1; + FINISH = 2; + CANCEL = 3; + PAUSE = 4; + RESUME = 5; +} + +// +// transaction requests +// + +message CreateBackupRequest { + required BackupId backup_id = 1; +} + +message CreateBackupReply { + required bytes token = 2; + optional uint32 tries = 3; +} + +message DeleteBackupRequest { + required BackupId backup_id = 1; +} + +message DeleteBackupReply { +} + +// +// untrusted messages +// + +message UntrustedMessageBatch { + repeated UntrustedMessage messages = 1; +} + +message UntrustedMessage { + oneof inner { + StartFrontendRequest start_frontend_request = 1; + StartReplicaRequest start_replica_request = 2; + StartReplicaGroupRequest start_replica_group_request = 3; + UntrustedTransactionRequest untrusted_transaction_request = 4; + UntrustedXferRequest untrusted_xfer_request = 5; + GetEnclaveStatusRequest get_enclave_status_request = 6; + + GetQeInfoReply get_qe_info_reply = 8; + GetQuoteReply get_quote_reply = 9; + GetAttestationReply get_attestation_reply = 10; + + NewMessageSignal new_message_signal = 11; + TimerTickSignal timer_tick_signal = 12; + SetFrontendConfigSignal set_frontend_config_signal = 13; + SetReplicaConfigSignal set_replica_config_signal = 14; + ResetPeerSignal reset_peer_signal = 15; + SetVerboseLoggingSignal set_verbose_logging_signal = 16; + } + reserved 7; +} + +message PartitionConfig { + required bytes group_id = 1; + optional PartitionKeyRangePB range = 2; + repeated bytes node_ids = 3; +} + +message StartFrontendRequest { + repeated PartitionConfig partitions = 1; + required EnclaveFrontendConfig config = 2; +} + +message EnclaveFrontendConfig { + required uint32 replica_timeout_ticks = 1; + required uint32 request_quote_ticks = 2; + required uint32 min_connect_timeout_ticks = 3; + required uint32 max_connect_timeout_ticks = 4; + required uint32 pending_request_count = 5; + required uint32 pending_request_ttl = 6; +} + +message SourcePartitionConfig { + required PartitionKeyRangePB range = 1; + repeated bytes node_ids = 2; +} + +message StartReplicaRequest { + required EnclaveReplicaConfig config = 1; +} + +message EnclaveReplicaConfig { + required uint32 election_timeout_ticks = 1; + required uint32 heartbeat_timeout_ticks = 2; + required uint32 request_quote_ticks = 3; + required uint32 min_connect_timeout_ticks = 4; + required uint32 max_connect_timeout_ticks = 5; + required uint32 attestation_expiry_ticks = 6; + required uint32 replication_chunk_size = 7; + required uint32 transfer_chunk_size = 8; + required uint32 storage_page_cache_size = 10; + required uint32 raft_log_index_page_cache_size = 13; + required uint32 max_frontend_count = 14; +} + +message StartReplicaGroupRequest { + repeated bytes peer_node_ids = 1; + required EnclaveReplicaGroupConfig config = 2; + optional SourcePartitionConfig source_partition = 3; +} + +message EnclaveReplicaGroupConfig { + required uint32 storage_size = 1; + required uint64 raft_log_data_size = 2; + required uint32 raft_log_index_size = 3; +} + +message NewMessageSignal { + required bytes node_id = 1; + required bytes data = 2; + required bool syn = 3; +} + +message TimerTickSignal { + required fixed64 now_secs = 1; +} + +message SetFrontendConfigSignal { + required EnclaveFrontendConfig config = 1; +} + +message SetReplicaConfigSignal { + required EnclaveReplicaConfig config = 1; +} + +message ResetPeerSignal { + required bytes peer_node_id = 1; +} + +message SetVerboseLoggingSignal { + required bool verbose_logging = 1; +} + +message GetQeInfoReply { + required bytes mrenclave = 1; + required uint64 flags = 2; + required uint64 xfrm = 3; + required uint32 misc_select = 4; + required uint32 config_svn = 5; + required bytes config_id = 6; +} + +message GetQuoteReply { + required bytes request_id = 1; + required bytes sgx_quote = 2; +} + +message GetAttestationReply { + required bytes request_id = 1; + required IasReport ias_report = 2; +} + +message UntrustedTransactionRequest { + required uint64 request_id = 1; + oneof data { + CreateBackupRequest create_backup_request = 2; + DeleteBackupRequest delete_backup_request = 3; + }; +} + +message UntrustedXferRequest { + required uint64 request_id = 1; + oneof data { + XferControlCommand xfer_control_command = 2; + }; +} + +message GetEnclaveStatusRequest { + required bool memory_status = 1; +} + +// +// enclave messages +// + +message EnclaveMessageBatch { + repeated EnclaveMessage messages = 1; +} + +message EnclaveMessage { + oneof inner { + StartFrontendReply start_frontend_reply = 1; + StartReplicaReply start_replica_reply = 2; + StartReplicaGroupReply start_replica_group_reply = 3; + UntrustedTransactionReply untrusted_transaction_reply = 4; + UntrustedXferReply untrusted_xfer_reply = 5; + GetEnclaveStatusReply get_enclave_status_reply = 6; + + SendMessageRequest send_message_request = 7; + GetQeInfoRequest get_qe_info_request = 8; + GetQuoteRequest get_quote_request = 9; + GetAttestationRequest get_attestation_request = 10; + + EnclaveLogSignal enclave_log_signal = 11; + EnclaveTransactionSignal enclave_transaction_signal = 12; + } +} + +message StartFrontendReply { + required bytes node_id = 1; +} + +message StartReplicaReply { + required bytes node_id = 1; +} + +message StartReplicaGroupReply { + optional ServiceId service_id = 1; + optional bytes group_id = 2; +} + +message GetEnclaveStatusReply { + oneof inner { + EnclaveReplicaStatus replica_status = 1; + EnclaveFrontendStatus frontend_status = 2; + } +} + +message SendMessageRequest { + required bytes node_id = 1; + required bytes data = 2; + required bool syn = 3; + optional bytes debug_msg = 4; +} + +message GetQeInfoRequest { +} + +message GetQuoteRequest { + required bytes request_id = 1; + required bytes sgx_report = 2; +} + +message GetAttestationRequest { + required bytes request_id = 1; + required bytes sgx_quote = 2; +} + +message UntrustedTransactionReply { + required uint64 request_id = 1; + oneof data { + CreateBackupReply create_backup_reply = 2; + DeleteBackupReply delete_backup_reply = 3; + }; +} + +enum UntrustedXferReplyStatus { + UNKNOWN = 0; + OK = 1; + NOT_LEADER = 2; + INVALID_STATE = 3; +} + +message UntrustedXferReply { + required uint64 request_id = 1; + required UntrustedXferReplyStatus status = 2; +} + +enum EnclaveLogLevel { + ERROR = 0; + WARN = 1; + INFO = 2; + DEBUG = 3; +} + +message EnclaveLogSignal { + required bytes message = 1; + required bytes module = 2; + required bytes file = 3; + required uint32 line = 4; + required EnclaveLogLevel level = 5; +} + +message EnclaveTransactionSignal { + required uint64 log_index = 1; + oneof transaction { + EnclaveFrontendRequestTransaction frontend_request = 2; + EnclaveStartXferTransaction start_xfer = 3; + EnclaveSetSidTransaction set_sid = 4; + EnclaveRemoveChunkTransaction remove_chunk = 5; + EnclaveApplyChunkTransaction apply_chunk = 6; + EnclavePauseXferTransaction pause_xfer = 7; + EnclaveResumeXferTransaction resume_xfer = 8; + EnclaveFinishXferTransaction finish_xfer = 9; + EnclaveSetTimeTransaction set_time = 10; + }; +} + +// +// enclave transactions +// + +message EnclaveFrontendRequestTransaction { + oneof transaction { + EnclaveCreateBackupTransaction create = 1; + EnclaveBackupTransaction backup = 2; + EnclaveRestoreTransaction restore = 3; + EnclaveDeleteBackupTransaction delete = 4; + + EnclaveTransactionErrorXferInProgress xfer_in_progress = 5; + EnclaveTransactionErrorWrongPartition wrong_partition = 6; + EnclaveTransactionErrorInvalidRequest invalid_request = 7; + EnclaveTransactionErrorInternalError internal_error = 8; + } +} +message EnclaveCreateBackupTransaction { + required BackupId backup_id = 1; +} +message EnclaveBackupTransaction { + required BackupId backup_id = 1; + required kbupd_client.BackupResponse.Status status = 2; +} +message EnclaveRestoreTransaction { + required BackupId backup_id = 1; + required kbupd_client.RestoreResponse.Status status = 2; +} +message EnclaveDeleteBackupTransaction { + required BackupId backup_id = 1; +} +message EnclaveTransactionErrorXferInProgress { +} +message EnclaveTransactionErrorWrongPartition { + required bool new_partition_unknown = 1; +} +message EnclaveTransactionErrorInvalidRequest { +} +message EnclaveTransactionErrorInternalError { +} +message EnclaveStartXferTransaction { +} +message EnclaveSetSidTransaction { + optional ServiceId service_id = 1; +} +message EnclaveRemoveChunkTransaction { + optional PartitionKeyRangePB chunk_range = 1; +} +message EnclaveApplyChunkTransaction { + optional PartitionKeyRangePB chunk_range = 1; + repeated BackupId chunk_ids = 2; +} +message EnclavePauseXferTransaction { +} +message EnclaveResumeXferTransaction { + optional PartitionKeyRangePB chunk_range = 1; +} +message EnclaveFinishXferTransaction { +} +message EnclaveSetTimeTransaction { + optional uint64 now_secs = 1; +} + +// +// enclave status +// + +message EnclaveMemoryStatus { + required uint32 footprint_bytes = 1; + required uint32 used_bytes = 2; + required uint32 free_chunks = 3; +} + +message EnclaveReplicaStatus { + optional EnclaveMemoryStatus memory_status = 1; + optional EnclaveReplicaPartitionStatus partition = 2; +} + +message EnclaveReplicaPartitionStatus { + required bytes group_id = 1; + optional bytes service_id = 2; + optional PartitionKeyRangePB range = 3; + repeated EnclavePeerStatus peers = 4; + required AttestationParameters min_attestation = 5; + required bool is_leader = 6; + required uint64 current_term = 7; + required uint64 prev_log_index = 8; + required uint64 last_applied_index = 9; + required uint64 commit_index = 10; + required uint64 last_log_index = 11; + required uint64 last_log_term = 12; + required uint64 log_data_length = 13; + required uint64 backup_count = 14; + + oneof xfer_status { + EnclaveIncomingXferStatus incoming_xfer_status = 15; + EnclaveOutgoingXferStatus outgoing_xfer_status = 16; + } +} + +message EnclavePeerStatus { + required bytes node_id = 1; + optional AttestationParameters attestation = 2; + optional EnclavePeerReplicationStatus replication_status = 3; + required bool is_leader = 4; + required uint64 inflight_requests = 5; + required uint64 unsent_requests = 6; +} + +message EnclavePeerReplicationStatus { + required uint64 next_index = 1; + required uint64 match_index = 2; + optional uint64 inflight_index = 3; + required bool probing = 4; +} + +message EnclaveIncomingXferStatus { + required PartitionKeyRangePB desired_range = 1; + repeated EnclavePeerStatus nodes = 2; +} + +message EnclaveOutgoingXferStatus { + required bytes group_id = 1; + required PartitionKeyRangePB full_xfer_range = 2; + optional PartitionKeyRangePB current_chunk_range = 3; + required bool paused = 4; + optional AttestationParameters min_attestation = 5; + repeated EnclavePeerStatus nodes = 6; +} + +message EnclaveFrontendStatus { + optional EnclaveMemoryStatus memory_status = 1; + repeated EnclaveFrontendPartitionStatus partitions = 2; + repeated EnclaveFrontendRangeStatus ranges = 3; +} + +message EnclaveFrontendPartitionStatus { + required bytes group_id = 1; + repeated EnclavePeerStatus nodes = 2; +} + +message EnclaveFrontendRangeStatus { + required PartitionKeyRangePB range = 1; + required bytes group_id = 2; +} + +// +// untrusted peer to untrusted peer messages +// + +message PeerConnectionHello { + required bytes node_id = 1; + optional PartitionConfig partition = 3; + reserved 2; + reserved 4; +} + +message PeerConnectionData { + required uint64 id = 1; + required bytes data = 2; + required bool syn = 3; +} + +message PeerConnectionDataAck { + required uint64 id = 1; +} + +message PeerConnectionMessage { + oneof inner { + PeerConnectionHello hello = 1; + PeerConnectionData data = 2; + PeerConnectionDataAck data_ack = 3; + } +} + +// +// control requests +// + +message ControlRequest { + required uint64 id = 1; + oneof data { + NegotiateClientRequest negotiate_client_request = 3; + ClientEncryptedRequest client_encrypted_request = 4; + GetStatusControlRequest get_status_control_request = 5; + ForcePeerReconnectRequest force_peer_reconnect_request = 7; + PeerDisconnectRequest peer_disconnect_request = 11; + PeerPermanentDeleteRequest peer_permanent_delete_request = 12; + XferControlRequest xfer_control_request = 8; + GetMetricsControlRequest get_metrics_control_request = 9; + TransactionControlRequest transaction_control_request = 10; + } +} + +message NegotiateClientRequest { + required string enclave_name = 1; + required bytes client_pubkey = 2; +} + +enum ClientRequestType { + NONE = 0; + BACKUP = 1; + RESTORE = 2; + DELETE = 3; +} + +message ClientEncryptedRequest { + required string enclave_name = 1; + required bytes backup_id = 4; + required ClientRequestType request_type = 5; + required ClientEncryptedMessage encrypted_message = 2; + required bytes pending_request_id = 3; +} + +message ClientEncryptedMessage { + required bytes iv = 1; + required bytes mac = 2; + required bytes data = 3; +} + +message GetStatusControlRequest { + required bool memory_status = 1; +} + +message TransactionControlRequest { + required string enclave_name = 1; + oneof data { + CreateBackupRequest create_backup_request = 2; + DeleteBackupRequest delete_backup_request = 3; + }; +} + +message ForcePeerReconnectRequest { + optional string enclave_name = 1; + required bytes node_id = 2; + optional string address = 3; +} + +message PeerDisconnectRequest { + optional string enclave_name = 1; + required bytes node_id = 2; +} + +message PeerPermanentDeleteRequest { + optional string enclave_name = 1; + required bytes node_id = 2; +} + +message XferControlRequest { + optional string enclave_name = 1; + required XferControlCommand xfer_control_command = 2; +} + +message GetMetricsControlRequest { +} + +// +// control replies +// + +message ControlReply { + required uint64 id = 1; + reserved 6; + oneof data { + NegotiateClientReply negotiate_client_reply = 3; + ClientEncryptedReply client_encrypted_reply = 4; + GetStatusControlReply get_status_control_reply = 5; + XferControlReply xfer_control_reply = 7; + GetMetricsControlReply get_metrics_control_reply = 8; + TransactionControlReply transaction_control_reply = 10; + + ControlErrorSignal control_error_signal = 2; + } +} + +message NegotiateClientReply { + required bytes server_static_pubkey = 1; + required bytes server_ephemeral_pubkey = 2; + required ClientEncryptedMessage encrypted_pending_request_id = 3; +} + +message ClientEncryptedReply { + required ClientEncryptedMessage encrypted_message = 1; +} + +message GetStatusControlReply { + repeated EnclaveStatus enclaves = 1; +} + +message EnclaveStatus { + required string name = 1; + required bytes node_id = 2; + oneof config { + EnclaveReplicaConfig replica_config = 3; + EnclaveFrontendConfig frontend_config = 4; + } + oneof status { + EnclaveReplicaStatus replica_status = 5; + EnclaveFrontendStatus frontend_status = 6; + } +} + +message XferControlReply { + required UntrustedXferReplyStatus status = 1; +} + +message GetMetricsControlReply { + required string metrics_json = 1; +} + +message TransactionControlReply { + oneof data { + CreateBackupReply create_backup_reply = 2; + DeleteBackupReply delete_backup_reply = 3; + } +} + +message ControlErrorSignal { + required string reason = 1; +} diff --git a/service/kbupd/src/lib.rs b/service/kbupd/src/lib.rs new file mode 100644 index 0000000..3425b13 --- /dev/null +++ b/service/kbupd/src/lib.rs @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(unused_parens)] + +use log::{error, warn, info, debug}; + +use try_future::*; + +#[macro_use] +mod metrics; + +mod actor; +mod api; +mod backup; +mod constants; +mod control; +mod enclave; +mod frontend; +mod intel_client; +mod limits; +#[cfg(test)] mod mocks; +mod peer; +mod protobufs_impl; +mod replica; +mod tls; +mod unix_signal; +mod util; + +pub mod logger; +pub mod protobufs; + +pub use crate::control::{ControlCodec, ControlListener}; +pub use crate::enclave::enclave::{Enclave, NodeId, SgxQuote}; +pub use crate::enclave::enclave_manager::{EnclaveManager, EnclaveManagerChannel, EnclaveManagerSender}; +pub use crate::enclave::error::*; +pub use crate::enclave::handshake_manager::{HandshakeManager}; +pub use crate::frontend::{FrontendService, FrontendCommandLineConfig}; +pub use crate::intel_client::{IntelClient}; +pub use crate::replica::{ReplicaService, ReplicaCommandLineConfig}; diff --git a/service/kbupd/src/limits/leaky_bucket.rs b/service/kbupd/src/limits/leaky_bucket.rs new file mode 100644 index 0000000..7fe72cd --- /dev/null +++ b/service/kbupd/src/limits/leaky_bucket.rs @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::time::{Instant}; + +use crate::util; + +pub struct LeakyBucketParameters { + pub size: u64, + pub leak_rate: f64, +} + +pub struct LeakyBucket { + remaining: f64, + last_update: Instant, +} + +impl LeakyBucket { + pub fn new(now: Instant, parameters: &LeakyBucketParameters) -> Self { + Self { + remaining: parameters.size as f64, + last_update: now, + } + } + + pub fn add(&mut self, amount: u64, now: Instant, parameters: &LeakyBucketParameters) -> Result<(), ()> { + let elapsed = util::duration::as_secs_f64(now.duration_since(self.last_update)); + let leaked = elapsed * parameters.leak_rate; + self.remaining = (self.remaining + leaked).min(parameters.size as f64); + self.last_update = now; + + let remaining = self.remaining - amount as f64; + if remaining >= 0.0 { + self.remaining = remaining; + Ok(()) + } else { + Err(()) + } + } +} + +#[cfg(test)] +mod test { + use std::time::{Duration, Instant}; + use std::u64; + + use super::*; + + #[test] + fn test_full() { + let parameters = LeakyBucketParameters { size: 60, leak_rate: 1.0 }; + let now = Instant::now(); + let mut bucket = LeakyBucket::new(now, ¶meters); + + assert!(bucket.add(u64::MAX, now, ¶meters).is_err()); + assert!(bucket.add(61, now, ¶meters).is_err()); + assert!(bucket.add(30, now, ¶meters).is_ok()); + assert!(bucket.add(30, now, ¶meters).is_ok()); + assert!(bucket.add(0, now, ¶meters).is_ok()); + assert!(bucket.add(1, now, ¶meters).is_err()); + assert!(bucket.add(u64::MAX, now, ¶meters).is_err()); + } + + #[test] + fn test_leak() { + let parameters = LeakyBucketParameters { size: 60, leak_rate: 1.0 }; + let now = Instant::now(); + let mut bucket = LeakyBucket::new(now, ¶meters); + + assert!(bucket.add(60, now, ¶meters).is_ok()); + + let now = now + Duration::from_secs(1); + + assert!(bucket.add(1, now, ¶meters).is_ok()); + assert!(bucket.add(1, now, ¶meters).is_err()); + + let now = now + Duration::from_secs(120); + + assert!(bucket.add(60, now, ¶meters).is_ok()); + assert!(bucket.add(1, now, ¶meters).is_err()); + } + + #[test] + fn test_leak_double() { + let parameters = LeakyBucketParameters { size: 60, leak_rate: 2.0 }; + let now = Instant::now(); + let mut bucket = LeakyBucket::new(now, ¶meters); + + assert!(bucket.add(60, now, ¶meters).is_ok()); + + let now = now + Duration::from_secs(1); + + assert!(bucket.add(1, now, ¶meters).is_ok()); + assert!(bucket.add(1, now, ¶meters).is_ok()); + assert!(bucket.add(1, now, ¶meters).is_err()); + + let now = now + Duration::from_secs(120); + + assert!(bucket.add(60, now, ¶meters).is_ok()); + assert!(bucket.add(1, now, ¶meters).is_err()); + } + + #[test] + fn test_leak_half() { + let parameters = LeakyBucketParameters { size: 60, leak_rate: 0.5 }; + let now = Instant::now(); + let mut bucket = LeakyBucket::new(now, ¶meters); + + assert!(bucket.add(60, now, ¶meters).is_ok()); + + let now = now + Duration::from_secs(1); + + assert!(bucket.add(1, now, ¶meters).is_err()); + + let now = now + Duration::from_secs(1); + + assert!(bucket.add(1, now, ¶meters).is_ok()); + assert!(bucket.add(1, now, ¶meters).is_err()); + + let now = now + Duration::from_secs(120); + + assert!(bucket.add(60, now, ¶meters).is_ok()); + assert!(bucket.add(1, now, ¶meters).is_err()); + } +} diff --git a/service/kbupd/src/limits/mod.rs b/service/kbupd/src/limits/mod.rs new file mode 100644 index 0000000..ffb57ba --- /dev/null +++ b/service/kbupd/src/limits/mod.rs @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod leaky_bucket; +pub mod rate_limiter; diff --git a/service/kbupd/src/limits/rate_limiter.rs b/service/kbupd/src/limits/rate_limiter.rs new file mode 100644 index 0000000..092d171 --- /dev/null +++ b/service/kbupd/src/limits/rate_limiter.rs @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::collections::{HashMap}; +use std::time::{Instant}; + +use failure::{Fail}; + +use super::leaky_bucket::*; +use crate::metrics::*; + +pub struct RateLimiter { + parameters: LeakyBucketParameters, + meter: Meter, + buckets: HashMap, +} + +#[derive(Debug, Fail)] +pub enum RateLimitError { + #[fail(display = "{}", _0)] + Exceeded(#[cause] RateLimitExceededError), + #[fail(display = "internal error")] + InternalError, +} + +#[derive(Clone, Debug, Fail)] +#[fail(display = "rate limit exceeded for {}, {}", key, amount)] +pub struct RateLimitExceededError { + pub key: String, + pub amount: u64, + pub leak_rate: f64, +} + +impl RateLimiter { + pub fn new(name: &str, parameters: LeakyBucketParameters) -> Self { + Self { + parameters, + meter: METRICS.metric(&metric_name(&[&metric_name!(""), name, "exceeded"])), + buckets: Default::default(), + } + } + + pub fn validate(&mut self, key: &str, amount: u64) -> Result<(), RateLimitError> { + let bucket = Self::bucket_mut(key, &mut self.buckets, &self.parameters); + + match bucket.add(amount, Instant::now(), &self.parameters) { + Ok(()) => Ok(()), + Err(()) => { + self.meter.mark(); + Err(RateLimitError::Exceeded(RateLimitExceededError { + key: key.to_string(), + amount, + leak_rate: self.parameters.leak_rate, + })) + } + } + } + + fn bucket_mut<'a>(key: &str, buckets: &'a mut HashMap, parameters: &LeakyBucketParameters) -> &'a mut LeakyBucket { + if buckets.contains_key(key) { + buckets.get_mut(key).unwrap_or_else(|| unreachable!()) + } else { + buckets.entry(key.to_string()) + .or_insert_with(|| LeakyBucket::new(Instant::now(), parameters)) + } + } +} + +// +// RateLimitError impls +// + +impl From for RateLimitError { + fn from(futures::Canceled: futures::Canceled) -> Self { + RateLimitError::InternalError + } +} diff --git a/service/kbupd/src/logger.rs b/service/kbupd/src/logger.rs new file mode 100644 index 0000000..4a1d2c5 --- /dev/null +++ b/service/kbupd/src/logger.rs @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::cell::*; +use std::fmt; +use std::io::{BufWriter, Stderr, Write}; + +use chrono::format::{StrftimeItems}; +use slog::{Drain, KV}; +use slog_async::{OverflowStrategy}; + +use crate::metrics::*; + +const ASYNC_QUEUE_SIZE: usize = 1024; +const STDERR_BUFFER_SIZE: usize = 65535; + +const TIMESTAMP_FORMAT: &str = "%Y-%m-%d %H:%M:%S.%3f"; + +#[derive(Clone)] +pub struct Logger { + slogger: slog::Logger, +} + +#[allow(dead_code)] +pub struct LoggerGuard { + slog_async_guard: slog_async::AsyncGuard, +} + +struct LoggerDrain { + drain: D, + level: slog::Level, +} + +struct StderrDrain { + buffer: RefCell>, +} + +struct FnSerializer(Fun); + +const EMPTY_SLOG_LOCATION: slog::RecordLocation = slog::RecordLocation { + file: "", + line: 0, + column: 0, + function: "", + module: "", +}; + +lazy_static::lazy_static! { + static ref ERROR_METER: Meter = METRICS.metric(&metric_name!("error")); + static ref WARN_METER: Meter = METRICS.metric(&metric_name!("warn")); + static ref INFO_METER: Meter = METRICS.metric(&metric_name!("info")); +} + +impl Logger { + pub fn new_with_guard(level: log::Level) -> (Self, LoggerGuard) { + let slog_stderr = StderrDrain::new().ignore_res(); + let slog_async = slog_async::Async::new(slog_stderr); + + let (slog_async, slog_async_guard) = slog_async.chan_size(ASYNC_QUEUE_SIZE) + .overflow_strategy(OverflowStrategy::DropAndReport) + .thread_name("logger".to_string()) + .build_with_guard(); + + let slog_drain = LoggerDrain { + drain: slog_async, + level: Self::slog_level(level) + }; + + let slogger = slog::Logger::root(slog_drain, slog::o!()); + (Self { slogger }, LoggerGuard { slog_async_guard }) + } + + pub fn slogger(&self) -> &slog::Logger { + &self.slogger + } + + fn slog_level(level: log::Level) -> slog::Level { + match level { + log::Level::Error => slog::Level::Error, + log::Level::Warn => slog::Level::Warning, + log::Level::Info => slog::Level::Info, + log::Level::Debug => slog::Level::Debug, + log::Level::Trace => slog::Level::Trace, + } + } +} + +impl log::Log for Logger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + self.slogger.is_enabled(Self::slog_level(metadata.level())) + } + + fn log(&self, record: &log::Record) { + let slog_record = slog::RecordStatic { + location: &EMPTY_SLOG_LOCATION, + level: Self::slog_level(record.level()), + tag: record.target(), + }; + self.slogger.log(&slog::Record::new(&slog_record, record.args(), slog::b!( + "location" => format_args!("{}:{}", + record.module_path().unwrap_or_default(), + record.line().unwrap_or_default()) + ))); + } + + fn flush(&self) { + } +} + +impl Drain for LoggerDrain +where D: Drain, +{ + type Ok = (); + type Err = slog::Never; + + fn log(&self, record: &slog::Record, values: &slog::OwnedKVList) -> Result { + match record.level() { + slog::Level::Error => ERROR_METER.mark(), + slog::Level::Warning => WARN_METER.mark(), + slog::Level::Info => INFO_METER.mark(), + _ => (), + } + + if record.level().is_at_least(self.level) { + let _ignore = self.drain.log(record, values); + } + Ok(()) + } + + fn is_enabled(&self, level: slog::Level) -> bool { + level.is_at_least(self.level) + } +} + +impl StderrDrain { + pub fn new() -> Self { + Self { + buffer: RefCell::new(BufWriter::with_capacity(STDERR_BUFFER_SIZE, std::io::stderr())), + } + } +} + +lazy_static::lazy_static! { + static ref TIMESTAMP_FORMAT_ITEMS: Box<[chrono::format::Item<'static>]> = StrftimeItems::new(TIMESTAMP_FORMAT).collect(); +} + +impl Drain for StderrDrain { + type Ok = (); + type Err = std::io::Error; + + fn log(&self, record: &slog::Record, values: &slog::OwnedKVList) -> Result { + let mut output = self.buffer.borrow_mut(); + + let syslog_severity = match record.level() { + slog::Level::Critical => '2', + slog::Level::Error => '3', + slog::Level::Warning => '4', + slog::Level::Info => '6', + slog::Level::Debug => '7', + slog::Level::Trace => '7', + }; + + let timespec = get_coarse_time(); + let datetime = chrono::NaiveDateTime::from_timestamp(timespec.0, timespec.1); + let formatted_timestamp = datetime.format_with_items(TIMESTAMP_FORMAT_ITEMS.iter().cloned()); + write!(output, "<{}>{} {:<5} [{}]", + syslog_severity, + formatted_timestamp, + record.level(), + record.tag())?; + + if !record.module().is_empty() { + write!(output, " {}:{}", record.module(), record.line())?; + } else { + record.kv().serialize(record, &mut FnSerializer(|key: slog::Key, val: &fmt::Arguments<'_>| { + if key == "location" { + write!(output, " {}", val) + } else { + Ok(()) + } + }))?; + } + + write!(output, " === {}", record.msg())?; + + let mut kv_serializer = FnSerializer(|key: slog::Key, val: &fmt::Arguments<'_>| { + if key != "location" { + write!(output, " {} [{}]", &key, val) + } else { + Ok(()) + } + }); + record.kv().serialize(record, &mut kv_serializer)?; + values.serialize(record, &mut kv_serializer)?; + + write!(output, "\n")?; + + output.flush()?; + + Ok(()) + } +} + +#[cfg(target_os = "linux")] +fn get_coarse_time() -> (i64, u32) { + let mut timespec = libc::timespec { + tv_sec: Default::default(), + tv_nsec: Default::default(), + }; + let _ignore = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME_COARSE, &mut timespec) }; + (timespec.tv_sec, timespec.tv_nsec as u32) +} +#[cfg(not(target_os = "linux"))] +fn get_coarse_time() -> (i64, u32) { + let timespec = time::get_time(); + (timespec.sec, timespec.nsec as u32) +} + +impl slog::Serializer for FnSerializer +where Fun: FnMut(slog::Key, &fmt::Arguments<'_>) -> std::io::Result<()>, +{ + fn emit_arguments(&mut self, key: slog::Key, val: &fmt::Arguments<'_>) -> slog::Result { + self.0(key, val).map_err(slog::Error::from) + } +} diff --git a/service/kbupd/src/main.rs b/service/kbupd/src/main.rs new file mode 100644 index 0000000..c34a3c2 --- /dev/null +++ b/service/kbupd/src/main.rs @@ -0,0 +1,634 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fs; +use std::io; +use std::io::prelude::*; +use std::path::{Path}; + +use failure::{bail, format_err, ResultExt}; +use log::{error}; + +use kbupd::*; +use kbupd_config::*; +use kbupd_config::replica::*; +use kbupd_config::frontend::*; +use kbupd_util::hex; +use kbuptlsd::prelude::*; + +fn main() { + let arguments = parse_arguments(); + + let log_level = if arguments.is_present("debug") { + log::Level::Debug + } else { + log::Level::Info + }; + + let (logger, logger_guard) = logger::Logger::new_with_guard(log_level); + log::set_boxed_logger(Box::new(logger)).expect("logger already set"); + log::set_max_level(log_level.to_level_filter()); + + match run(arguments) { + Ok(()) => (), + Err(error) => { + error!("initialization error: {:?}", error); + } + } + + drop(logger_guard); + std::process::exit(1); +} + +fn run(arguments: clap::ArgMatches<'static>) -> Result<(), failure::Error> { + let (subcommand_name, subcommand_arguments) = arguments.subcommand(); + + let subcommand_arguments = subcommand_arguments.expect("no subcommand arguments"); + let enclave_directory = Path::new(arguments.value_of("enclave_directory").expect("no enclave_directory")); + let config_file_arg = Path::new(arguments.value_of("config_file").expect("no config_file")); + let config_dir_arg = arguments.value_of("config_dir").map(Path::new); + let full_hostname = arguments.value_of("full_hostname"); + let kbuptlsd_bin_path = Path::new(arguments.value_of("kbuptlsd_bin_file").expect("no kbuptlsd_bin_file")); + let background = arguments.is_present("background"); + let pid_file = arguments.value_of("pid_file").map(open_pid_file).unwrap_or(Ok(None))?; + + let maybe_background_pipe = if background { + Some(daemonize(pid_file)?) + } else { + None + }; + + let config_file_dir = config_file_arg.parent().unwrap_or(Path::new(".")); + let (config_directory, config_file_path) = match config_dir_arg { + Some(config_directory) => (config_directory, config_directory.join(config_file_arg)), + None => (config_file_dir, config_file_arg.to_owned()), + }; + + let config_file = fs::File::open(&config_file_path) + .with_context(|_| format_err!("error opening config file {}", config_file_path.display()))?; + + let service = match subcommand_name { + "replica" => { + let mut config: ReplicaConfig = serde_yaml::from_reader(config_file) + .with_context(|_| format_err!("error reading config file {}", config_file_path.display()))?; + + set_argument( &mut config.attestation.acceptGroupOutOfDate, Some(arguments.is_present("ias_accept_group_out_of_date")).filter(|present| *present)); + set_argument( &mut config.attestation.tlsConfigPath, arguments.value_of("ias_tls_config_file").map(Path::new)); + set_argument( &mut config.attestation.disabled, Some(!arguments.is_present("ias_tls_config_file")).filter(|present| !*present)); + set_argument( &mut config.attestation.host, arguments.value_of("ias_host")); + parse_argument(&mut config.attestation.spid, arguments.value_of("ias_spid"), hex::parse_fixed).context("invalid --ias-spid")?; + set_argument( &mut config.control.listenHostPort, arguments.value_of("control_listen_address")); + set_argument( &mut config.enclave.mrenclave, subcommand_arguments.value_of("enclave_filename")); + parse_argument(&mut config.enclave.debug, subcommand_arguments.value_of("enclave_debug"), parse_yes_no).context("invalid --enclave-debug")?; + parse_argument(&mut config.enclave.electionTimeoutMs, subcommand_arguments.value_of("election_timeout_ms"), str::parse).context("invalid --election-timeout-ms")?; + parse_argument(&mut config.enclave.electionHeartbeats, subcommand_arguments.value_of("election_heartbeats"), str::parse).context("invalid --election-heartbeats")?; + parse_argument(&mut config.enclave.storageSize, subcommand_arguments.value_of("storage_size"), str::parse).context("invalid --storage-size")?; + set_argument( &mut config.enclave.listenHostPort, subcommand_arguments.value_of("peer_listen_address")); + + if let Some(replicas) = subcommand_arguments.values_of("replicas") { + config.enclave.replicas = replicas.into_iter().map(|address: &str| ReplicaPeerConfig { hostPort: address.to_string() }).collect(); + } + if let Some(source_replicas) = subcommand_arguments.values_of("source_replicas") { + config.enclave.sourcePartition = Some(ReplicaSourcePartitionConfig { + firstBackupId: hex::parse_fixed(subcommand_arguments.value_of("firstid").expect("no firstid")).context("invalid --firstid")?, + lastBackupId: hex::parse_fixed(subcommand_arguments.value_of("lastid").expect("no lastid")).context("invalid --lastid")?, + replicas: source_replicas.into_iter().map(|address: &str| ReplicaPeerConfig { hostPort: address.to_string() }).collect(), + }); + } + + let cmdline_config = ReplicaCommandLineConfig { + enclave_directory, + config_directory, + kbuptlsd_bin_path, + full_hostname, + exit_signals_enabled: subcommand_arguments.is_present("exit_signals"), + }; + + let peer_ca_file = Path::new(subcommand_arguments.value_of("peer_ca_file").expect("no peer_ca_file")); + let peer_key_file = Path::new(subcommand_arguments.value_of("peer_key_file").expect("no peer_key_file")); + + let peer_tls_client_args = TlsClientProxyArguments::NoConfig { + ca: TlsClientProxyCaArgument::CustomPemFile(peer_ca_file.to_owned()), + key_file: Some(peer_key_file.to_owned()), + hostname: TlsClientProxyHostnameArgument::AllowInvalid, + }; + + let peer_tls_server_args = TlsProxyListenerArguments::NoConfig { + ca_file: peer_ca_file.to_owned(), + key_file: peer_key_file.to_owned(), + }; + + Service::Replica(ReplicaService::start(config, cmdline_config, peer_tls_server_args, peer_tls_client_args)?) + } + "frontend" => { + let mut config: FrontendConfig = serde_yaml::from_reader(config_file) + .with_context(|_| format_err!("error reading config file {}", config_file_path.display()))?; + + set_argument( &mut config.api.listenHostPort, subcommand_arguments.value_of("api_listen_address")); + parse_argument(&mut config.api.userAuthenticationTokenSharedSecret, subcommand_arguments.value_of("api_signal_user_token_secret"), hex::parse).context("invalid --api-signal-user-token-secret")?; + set_argument( &mut config.attestation.acceptGroupOutOfDate, Some(arguments.is_present("ias_accept_group_out_of_date")).filter(|present| *present)); + set_argument( &mut config.attestation.tlsConfigPath, arguments.value_of("ias_tls_config_file").map(Path::new)); + set_argument( &mut config.attestation.disabled, Some(!arguments.is_present("ias_tls_config_file")).filter(|present| !*present)); + set_argument( &mut config.attestation.host, arguments.value_of("ias_host")); + parse_argument(&mut config.attestation.spid, arguments.value_of("ias_spid"), hex::parse_fixed).context("invalid --ias-spid")?; + + set_argument( &mut config.control.listenHostPort, arguments.value_of("control_listen_address")); + + if let Some(enclave_name) = subcommand_arguments.value_of("enclave_name") { + let enclave_config = (config.enclaves.iter_mut()) + .find(|enclave_config: &&mut FrontendEnclaveConfig| enclave_config.name == enclave_name); + let enclave_config = if let Some(enclave_config) = enclave_config { + enclave_config + } else { + config.enclaves.push(FrontendEnclaveConfig { + name: enclave_name.to_string(), + mrenclave: Default::default(), + debug: false, + partitions: Default::default(), + electionTimeoutMs: 1000, + + pendingRequestCount: 32768, + pendingRequestTtlMs: 0, + }); + config.enclaves.last_mut().unwrap_or_else(|| unreachable!()) + }; + set_argument( &mut enclave_config.mrenclave, subcommand_arguments.value_of("enclave_filename")); + parse_argument(&mut enclave_config.debug, subcommand_arguments.value_of("enclave_debug"), parse_yes_no).context("invalid --enclave-debug")?; + parse_argument(&mut enclave_config.partitions, subcommand_arguments.values_of("partitions"), parse_partition_specs)?; + parse_argument(&mut enclave_config.electionTimeoutMs, subcommand_arguments.value_of("election_timeout_ms"), str::parse::).context("invalid --election-timeout-ms")?; + } + + let peer_ca_file = Path::new(subcommand_arguments.value_of("peer_ca_file").expect("no peer_ca_file")); + let peer_key_file = Path::new(subcommand_arguments.value_of("peer_key_file").expect("no peer_key_file")); + + let cmdline_config = FrontendCommandLineConfig { + enclave_directory, + config_directory, + kbuptlsd_bin_path, + full_hostname, + }; + + let peer_tls_args = TlsClientProxyArguments::NoConfig { + ca: TlsClientProxyCaArgument::CustomPemFile(peer_ca_file.to_owned()), + key_file: Some(peer_key_file.to_owned()), + hostname: TlsClientProxyHostnameArgument::AllowInvalid, + }; + + Service::Frontend(FrontendService::start(config, cmdline_config, peer_tls_args)?) + } + _ => unreachable!("no subcommand"), + }; + + if let Some(background_pipe) = maybe_background_pipe { + let _ignore = background_pipe.ack(0); + } + + service.join(); + Ok(()) +} + +enum Service { + Frontend(FrontendService), + Replica(ReplicaService), +} + +impl Service { + pub fn join(self) { + match self { + Service::Frontend(frontend) => frontend.join(), + Service::Replica(replica) => replica.join(), + } + } +} + +fn open_pid_file(path: &str) -> Result, failure::Error> { + let file = fs::OpenOptions::new().read(true) + .write(true) + .create_new(true) + .open(path) + .with_context(|_| format_err!("error opening pid file {}", path))?; + Ok(Some(file)) +} + +fn set_argument(to_field: &mut U, maybe_argument: Option) +where U: From +{ + if let Some(argument) = maybe_argument { + *to_field = U::from(argument); + } +} + +fn parse_argument(to_field: &mut U, maybe_argument: Option, parse_fun: F) -> Result<(), E> +where F: Fn(T) -> Result +{ + if let Some(argument) = maybe_argument { + *to_field = parse_fun(argument)?; + } + Ok(()) +} + +fn parse_yes_no(argument: &str) -> Result { + match argument { + "yes" | "" => Ok(true), + "no" => Ok(false), + _ => bail!("invalid yes/no value: {}", argument), + } +} + +fn parse_partition_specs<'a>(partition_specs: impl Iterator) + -> Result, failure::Error> { + let mut partitions: Vec = Vec::new(); + for partition_specs in partition_specs { + let mut partition_spec_split = partition_specs.splitn(2, '='); + + let key_range_spec = partition_spec_split.next().unwrap_or_default(); + + let range = if key_range_spec.len() != 0 { + let mut key_range_spec_split = key_range_spec.splitn(2, '-'); + Some(FrontendPartitionRangeConfig { + firstBackupId: hex::parse_fixed(key_range_spec_split.next().unwrap_or_default()).context("invalid partition key range")?, + lastBackupId: hex::parse_fixed(key_range_spec_split.next().unwrap_or_default()).context("invalid partition key range")?, + }) + } else { + None + }; + + let replica_host_ports_spec = partition_spec_split.next().unwrap_or_default(); + + let mut replicas: Vec = Vec::new(); + for replica_host_port in replica_host_ports_spec.split(',') { + replicas.push(FrontendPartitionReplicaConfig { + hostPort: replica_host_port.to_string(), + }); + } + + partitions.push(FrontendPartitionConfig { + range, + replicas, + }); + } + Ok(partitions) +} + +fn parse_arguments() -> clap::ArgMatches<'static> { + let enclave_filename_argument = + clap::Arg::with_name("enclave_filename") + .takes_value(true) + .long("enclave") + .value_name("enclave_filename") + .help("Filename of libkbupd_enclave.so"); + + let enclave_debug_argument = + clap::Arg::with_name("enclave_debug") + .long("enclave-debug") + .takes_value(true) + .min_values(0) + .possible_values(&["yes", "no"]) + .help("run enclave in SGX debug mode"); + + let replicas_argument = + clap::Arg::with_name("replicas") + .multiple(true) + .value_delimiter(",") + .long("replicas") + .value_name("node_spec") + .help("Set of comma-separated node_specs of replicas, where node_spec is hex_node_id=hostname:port"); + + let source_replicas_argument = + clap::Arg::with_name("source_replicas") + .requires("firstid") + .requires("lastid") + .multiple(true) + .value_delimiter(",") + .long("source-nodes") + .value_name("node_spec") + .help("Set of comma-separated node_specs of source replicas, where node_spec is hex_node_id=hostname:port"); + + let firstid_argument = + clap::Arg::with_name("firstid") + .long("firstid") + .value_name("backup_id") + .help("First BackupId owned by this cluster"); + let lastid_argument = + clap::Arg::with_name("lastid") + .long("lastid") + .value_name("backup_id") + .help("Last BackupId owned by this cluster"); + + let election_timeout_ms_argument = + clap::Arg::with_name("election_timeout_ms") + .long("election-timeout-ms") + .value_name("election_timeout_ms") + .help("Raft group election timeout in milliseconds"); + + let election_heartbeats_argument = + clap::Arg::with_name("election_heartbeats") + .long("election-heartbeats") + .value_name("heartbeat_count") + .help("Number of heartbeats per raft group election timeout period"); + + let storage_size_argument = + clap::Arg::with_name("storage_size") + .takes_value(true) + .long("storage-size") + .value_name("backup_count") + .help("Storage capacity for key backup entry data, in number of backups"); + + let peer_key_file_argument = + clap::Arg::with_name("peer_key_file") + .takes_value(true) + .required(true) + .long("peer-key-file") + .value_name("pkcs12_path") + .help("Path to DER-encoded PKCS12 client certificate and private key used to authenticate with peers"); + + let peer_ca_file_argument = + clap::Arg::with_name("peer_ca_file") + .takes_value(true) + .required(true) + .long("peer-ca-file") + .value_name("certificate_path") + .help("Path to PEM-encoded CA certificate used to authenticate peers"); + + let peer_listen_argument = + clap::Arg::with_name("peer_listen_address") + .takes_value(true) + .long("listen-peers") + .value_name("listen_address") + .help("ip[:port] address to listen for peers on"); + + let exit_signals_argument = + clap::Arg::with_name("exit_signals") + .long("exit-signals") + .help("Forcefully quit on SIGTERM/SIGQUIT/SIGINT signal"); + + let replica_subcommand = + clap::SubCommand::with_name("replica") + .arg(peer_listen_argument) + .arg(peer_ca_file_argument.clone()) + .arg(peer_key_file_argument.clone()) + .arg(replicas_argument) + .arg(source_replicas_argument) + .arg(firstid_argument) + .arg(lastid_argument) + .arg(election_timeout_ms_argument.clone()) + .arg(election_heartbeats_argument) + .arg(storage_size_argument) + .arg(enclave_filename_argument.clone()) + .arg(enclave_debug_argument.clone()) + .arg(exit_signals_argument) + .about("Starts a replica kbupd node"); + + let api_listen_argument = + clap::Arg::with_name("api_listen_address") + .takes_value(true) + .long("listen-api") + .value_name("listen_address") + .help("ip[:port] address to listen for HTTP API connections on"); + + let api_signal_user_token_secret_argument = + clap::Arg::with_name("api_signal_user_token_secret") + .takes_value(true) + .long("api-signal-user-token-secret") + .value_name("shared_secret") + .help("Secret shared with Signal Server to authenticate signal users, in hexadecimal"); + + let partitions_argument = + clap::Arg::with_name("partitions") + .requires("enclave_name") + .multiple(true) + .value_delimiter(";") + .long("partitions") + .value_name("partition_spec") + .help("Set of semicolon-separated partitions, where partition_spec is hex_lower_bound_key-hex_upper_bound_key=replica_host_port,... or =replica_host_port,..."); + + let enclave_name_argument = + clap::Arg::with_name("enclave_name") + .takes_value(true) + .long("enclave-name") + .value_name("enclave_name") + .help("Name of enclave to use in client API (e.g. lowercase hexadecimal mrenclave value)"); + + let frontend_subcommand = + clap::SubCommand::with_name("frontend") + .arg(api_listen_argument) + .arg(api_signal_user_token_secret_argument) + .arg(peer_ca_file_argument) + .arg(peer_key_file_argument) + .arg(partitions_argument) + .arg(election_timeout_ms_argument.requires("enclave_name")) + .arg(enclave_name_argument) + .arg(enclave_filename_argument.requires("enclave_name")) + .arg(enclave_debug_argument.requires("enclave_name")) + .about("Starts a frontend kbupd node"); + + let debug_argument = + clap::Arg::with_name("debug") + .long("debug") + .help("emit debug logging"); + + let background_argument = + clap::Arg::with_name("background") + .long("background") + .help("run process in the background after initialization"); + + let pid_file_argument = + clap::Arg::with_name("pid_file") + .takes_value(true) + .long("pid-file") + .value_name("pid_file") + .help("file path to write pid to after initialization"); + + let config_file_argument = + clap::Arg::with_name("config_file") + .takes_value(true) + .required(true) + .long("config-file") + .value_name("config_file_path") + .help("Path to YAML config file, relative to --config-dir if specified"); + + let config_dir_argument = + clap::Arg::with_name("config_dir") + .takes_value(true) + .long("config-dir") + .value_name("config_dir_path") + .help("Path to directory containing YAML config files, defaults to parent of --config-file"); + + let full_hostname_argument = + clap::Arg::with_name("full_hostname") + .takes_value(true) + .long("full-hostname") + .value_name("fqdn") + .help("Hostname FQDN to use when reporting metrics"); + + let kbuptlsd_bin_file_argument = + clap::Arg::with_name("kbuptlsd_bin_file") + .takes_value(true) + .required(true) + .long("kbuptlsd-bin-file") + .value_name("kbuptlsd_bin_path") + .help("Path to kbuptlsd binary"); + + let enclave_directory_argument = + clap::Arg::with_name("enclave_directory") + .takes_value(true) + .required(true) + .long("enclave-directory") + .value_name("enclave_directory") + .help("Path to directory containing enclaves"); + + let ias_spid_argument = + clap::Arg::with_name("ias_spid") + .takes_value(true) + .long("ias-spid") + .value_name("spid_hex") + .help("SGX SPID value in hexadecimal"); + + let ias_host_argument = + clap::Arg::with_name("ias_host") + .takes_value(true) + .long("ias-host") + .value_name("url") + .help("base URL, with scheme and port, used to access Intel Attestation Services"); + + let ias_tls_config_file_argument = + clap::Arg::with_name("ias_tls_config_file") + .takes_value(true) + .long("ias-tls-config-file") + .value_name("ias_tls_config_file_path") + .help("Path to kbuptlsd YAML config file used to access Intel Attestation Services"); + + let ias_accept_group_out_of_date_argument = + clap::Arg::with_name("ias_accept_group_out_of_date") + .long("ias-accept-group-out-of-date") + .help("allow serving Intel Attestation responses having a status of GROUP_OUT_OF_DATE instead of OK"); + + let control_listen_argument = + clap::Arg::with_name("control_listen_address") + .takes_value(true) + .long("listen-control") + .value_name("listen_address") + .help("ip[:port] address to listen for control connections on"); + + clap::App::new(clap::crate_name!()) + .version(clap::crate_version!()) + .about(clap::crate_description!()) + .author(clap::crate_authors!()) + .setting(clap::AppSettings::VersionlessSubcommands) + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .arg(debug_argument) + .arg(background_argument) + .arg(pid_file_argument) + .arg(config_file_argument) + .arg(config_dir_argument) + .arg(full_hostname_argument) + .arg(kbuptlsd_bin_file_argument) + .arg(enclave_directory_argument) + .arg(ias_spid_argument) + .arg(ias_host_argument) + .arg(ias_tls_config_file_argument) + .arg(ias_accept_group_out_of_date_argument) + .arg(control_listen_argument) + .subcommand(replica_subcommand) + .subcommand(frontend_subcommand) + .get_matches() +} + +struct BackgroundPipe(i32); +impl BackgroundPipe { + fn ack(self, exit_code: u8) -> Result<(), io::Error> { + let buf: [u8; 1] = [exit_code; 1]; + loop { + let written = unsafe { libc::write(self.0, buf.as_ptr() as *const libc::c_void, 1) }; + if written == 1 { + break Ok(()); + } else if written == 0 { + break Err(io::Error::from(io::ErrorKind::WriteZero)); + } else { + let error = io::Error::last_os_error(); + if error.kind() != io::ErrorKind::Interrupted { + break Err(error); + } + } + } + } +} +impl Drop for BackgroundPipe { + fn drop(&mut self) { + let _ignore = unsafe { libc::close(self.0) }; + } +} + +fn daemonize(maybe_pid_file: Option) -> Result { + let mut pipe: [i32; 2] = [0; 2]; + if unsafe { libc::pipe2(pipe.as_mut_ptr(), libc::O_CLOEXEC) } == -1 { + return Err(io::Error::last_os_error().into()); + } + + if fork().context("error forking")? != 0 { + let _ignore = unsafe { libc::close(pipe[1]) }; + + let mut buf: [u8; 1] = [0; 1]; + let maybe_ack = loop { + let read = unsafe { libc::read(pipe[0], buf.as_mut_ptr() as *mut libc::c_void, buf.len()) }; + if read == 1 { + break Ok(buf[0]); + } else if read == 0 { + break Err(io::Error::from(io::ErrorKind::BrokenPipe)); + } else if read == -1 { + let error = io::Error::last_os_error(); + if error.kind() != io::ErrorKind::Interrupted { + break Err(error); + } + } + }; + if let Ok(ack) = maybe_ack { + std::process::exit(ack as i32); + } else { + std::process::exit(1); + } + } + let _ignore = unsafe { libc::close(pipe[0]) }; + let background_pipe = BackgroundPipe(pipe[1]); + + std::env::set_current_dir(&std::path::Path::new("/")).context("error setting current directory")?; + + if unsafe { libc::setsid() } == -1 { + return Err(io::Error::last_os_error().into()) + } + + if fork().context("error double forking")? != 0 { + std::process::exit(0); + } + + if let Some(mut pid_file) = maybe_pid_file { + let pid: u32 = std::process::id(); + write!(&mut pid_file, "{}\n", pid).context("error writing pid file")?; + pid_file.flush().context("error writing pid file")?; + } + + Ok(background_pipe) +} + +fn fork() -> Result { + let pid = unsafe { libc::fork() }; + if pid < 0 { + Err(io::Error::from_raw_os_error(pid)) + } else { + Ok(pid) + } +} diff --git a/service/kbupd/src/metrics/config.rs b/service/kbupd/src/metrics/config.rs new file mode 100644 index 0000000..e69de29 diff --git a/service/kbupd/src/metrics/json_reporter.rs b/service/kbupd/src/metrics/json_reporter.rs new file mode 100644 index 0000000..db16f2c --- /dev/null +++ b/service/kbupd/src/metrics/json_reporter.rs @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::borrow::{Cow}; +use std::collections::{HashMap}; +use std::time::*; + +use exponential_decay_histogram::{Snapshot}; +use failure::{ResultExt}; +use http::{HttpTryFrom, Uri}; +use http::header::{HeaderValue}; +use http::uri; +use hyper::{Body, Client, Method, Request}; +use hyper::client::connect::{Connect}; +use log::{warn, info, debug}; +use nix::unistd; +use serde_derive::*; + +use super::*; + +pub struct JsonReporter { + uri: Uri, + client: Client, + runtime: tokio::runtime::Runtime, +} + +#[derive(Default, Deserialize, Serialize)] +pub struct MetricsReport(HashMap); + +#[derive(Deserialize, Serialize)] +#[serde(untagged)] +enum MetricReport { + Counter(u64), + Gauge(f64), + Meter(MeterReport), + Timer(TimerReport), + Histogram(HistogramReport), +} + +#[derive(Deserialize, Serialize)] +struct MeterReport { + count: u64, + mean: f64, + m1: f64, + m5: f64, + m15: f64, +} + +#[derive(Deserialize, Serialize)] +struct SnapshotReport { + max: i64, + mean: f64, + min: i64, + stddev: f64, + median: i64, + p75: i64, + p95: i64, + p98: i64, + p99: i64, + p999: i64, +} + +#[derive(Deserialize, Serialize)] +struct TimerReport { + rate: MeterReport, + duration: SnapshotReport, +} + +#[derive(Deserialize, Serialize)] +struct HistogramReport { + count: u64, + #[serde(flatten)] + snapshot: SnapshotReport, +} + +impl JsonReporter +where ConnectorTy: Connect + 'static, +{ + pub fn new(token: &str, hostname: &str, maybe_our_hostname: Option<&str>, connector: ConnectorTy) -> Result { + let our_hostname = match maybe_our_hostname { + Some(our_hostname) => our_hostname.into(), + None => { + let mut hostname_buf = [0; 255]; + let hostname_cstr = unistd::gethostname(&mut hostname_buf).context("error getting hostname")?; + Cow::Owned(hostname_cstr.to_string_lossy().into_owned()) + } + }; + + info!("starting json metrics reporter for {} as {}", hostname, our_hostname); + + let path_and_query = format!("/report/metrics?t={}&h={}", token, our_hostname); + let mut uri_parts = uri::Parts::default(); + uri_parts.scheme = Some(uri::Scheme::HTTPS); + uri_parts.authority = Some(uri::Authority::try_from(hostname).context("invalid hostname")?); + uri_parts.path_and_query = Some(uri::PathAndQuery::try_from(path_and_query.as_str()).context("invalid token or host")?); + let uri = Uri::try_from(uri_parts).context("invalid hostname, token, or host")?; + + let runtime = tokio::runtime::Builder::new().core_threads(1) + .name_prefix("json-reporter-") + .build() + .context("error starting tokio runtime for json-reporter")?; + let client = Client::builder().executor(runtime.executor()).build(connector); + + Ok(Self { + uri, + client, + runtime, + }) + } +} + +impl Reporter for JsonReporter +where ConnectorTy: Connect + 'static, +{ + fn report(&mut self, registry: &MetricRegistry) { + debug!("reporting metrics..."); + + let metrics_report = MetricsReport::from(registry); + let encoded_request = match serde_json::to_vec(&metrics_report) { + Ok(encoded_request) => encoded_request, + Err(serde_error) => { + warn!("error encoding json metrics: {}", serde_error); + return; + } + }; + + let mut hyper_request = Request::new(Body::from(encoded_request)); + *hyper_request.method_mut() = Method::POST; + *hyper_request.uri_mut() = self.uri.clone(); + hyper_request.headers_mut().insert("Content-Type", HeaderValue::from_static("application/json")); + + let response = self.client.request(hyper_request); + + match self.runtime.block_on(response) { + Ok(response) => { + if response.status().is_success() { + debug!("sent {} metrics successfully", metrics_report.0.len()); + } else { + info!("http error sending metrics: {}", response.status()); + } + } + Err(hyper_error) => { + info!("error sending metrics: {}", hyper_error); + } + } + } +} + +impl From<&MetricRegistry> for MetricsReport { + fn from(registry: &MetricRegistry) -> Self { + let mut report = Self::default(); + let now = Instant::now(); + + for (metric_name, metric) in registry.metrics() { + let metric_report = MetricReport::from_metric(&metric, now); + report.0.insert(metric_name, metric_report); + } + report + } +} + +// +// MetricReport impls +// + +impl MetricReport { + fn from_metric(metric: &Metric, now: Instant) -> Self { + match metric { + Metric::Counter(counter) => MetricReport::Counter(counter.count()), + Metric::Gauge(gauge) => MetricReport::Gauge(gauge.value()), + Metric::Meter(meter) => MetricReport::Meter(MeterReport::from_meter(meter, now)), + Metric::Histogram(histogram) => MetricReport::Histogram(histogram.into()), + Metric::Timer(timer) => MetricReport::Timer(TimerReport::from_timer(timer, now)), + } + } +} + +impl MeterReport { + fn from_meter(meter: &Meter, now: Instant) -> Self { + let elapsed = meter.tick(now); + let count = meter.count(); + Self { + count, + mean: (count as f64) / ((elapsed.as_nanos() as f64) / 1e9), + m1: meter.m1_rate(), + m5: meter.m5_rate(), + m15: meter.m15_rate(), + } + } +} + +impl From<&Histogram> for HistogramReport { + fn from(histogram: &Histogram) -> Self { + let snapshot = histogram.snapshot(); + Self { + count: snapshot.count(), + snapshot: SnapshotReport::from(&snapshot), + } + } +} + +impl From<&Snapshot> for SnapshotReport { + fn from(snapshot: &Snapshot) -> Self { + Self { + max: snapshot.max(), + mean: snapshot.mean(), + min: snapshot.min(), + stddev: snapshot.stddev(), + median: snapshot.value(0.5), + p75: snapshot.value(0.75), + p95: snapshot.value(0.95), + p98: snapshot.value(0.98), + p99: snapshot.value(0.99), + p999: snapshot.value(0.999), + } + } +} + +impl TimerReport { + fn from_timer(timer: &Timer, now: Instant) -> Self { + Self { + rate: MeterReport::from_meter(timer.meter(), now), + duration: HistogramReport::from(timer.histogram()).snapshot, + } + } +} diff --git a/service/kbupd/src/metrics/macros.rs b/service/kbupd/src/metrics/macros.rs new file mode 100644 index 0000000..2801130 --- /dev/null +++ b/service/kbupd/src/metrics/macros.rs @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +macro_rules! metric_module_name { + () => (module_path!().replace("::", ".")); +} + +macro_rules! metric_name { + ($($args:literal),+) => ({ + let mut name = metric_module_name!(); + name.push_str(concat!($(".", $args),+)); + name + }); +} diff --git a/service/kbupd/src/metrics/metrics.rs b/service/kbupd/src/metrics/metrics.rs new file mode 100644 index 0000000..1cc29a2 --- /dev/null +++ b/service/kbupd/src/metrics/metrics.rs @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::convert::*; +use std::sync::*; +use std::sync::atomic::*; +use std::time::*; + +use exponential_decay_histogram::*; + +pub const METER_INTERVAL: Duration = Duration::from_secs(5); + +#[derive(Clone)] +pub enum Metric { + Counter(Counter), + Gauge(Gauge), + Meter(Meter), + Histogram(Histogram), + Timer(Timer), +} + +#[derive(Clone, Default)] +pub struct Counter { + count: Arc, +} + +pub struct CounterGuard { + counter: Counter, + value: u64, +} + +#[derive(Clone, Default)] +pub struct Gauge { + value: Arc, +} + +#[derive(Clone, Default)] +pub struct Meter { + shared: Arc, +} + +#[derive(Clone, Default)] +pub struct Histogram { + sample: Arc>, +} + +#[derive(Clone, Default)] +pub struct Timer { + meter: Meter, + histogram: Histogram, +} + +pub struct TimerGuard<'a> { + timer: &'a Timer, + start: Instant, +} + +struct MeterShared { + start_time: Instant, + last_tick: AtomicU64, + tick_total: AtomicU64, + count: AtomicU64, + m1_rate: ExponentiallyWeightedMovingAverage, + m5_rate: ExponentiallyWeightedMovingAverage, + m15_rate: ExponentiallyWeightedMovingAverage, +} + +struct ExponentiallyWeightedMovingAverage { + alpha: f64, + average: AtomicU64, +} + +// +// Metric impls +// + +impl From for Metric { + fn from(counter: Counter) -> Self { + Metric::Counter(counter) + } +} + +impl From for Metric { + fn from(gauge: Gauge) -> Self { + Metric::Gauge(gauge) + } +} + +impl From for Metric { + fn from(meter: Meter) -> Self { + Metric::Meter(meter) + } +} + +impl From for Metric { + fn from(histogram: Histogram) -> Self { + Metric::Histogram(histogram) + } +} + +impl From for Metric { + fn from(timer: Timer) -> Self { + Metric::Timer(timer) + } +} + +// +// Counter impls +// + +impl Counter { + pub fn inc(&self, value: u64) { + self.count.fetch_add(value, Ordering::SeqCst); + } + pub fn dec(&self, value: u64) { + self.count.fetch_sub(value, Ordering::SeqCst); + } + pub fn guard(&self, value: u64) -> CounterGuard { + self.inc(value); + CounterGuard { + counter: self.clone(), + value, + } + } + pub fn count(&self) -> u64 { + self.count.load(Ordering::SeqCst) + } +} + +impl Eq for Counter {} +impl PartialEq for Counter { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.count, &other.count) + } +} + +impl TryFrom for Counter { + type Error = (); + fn try_from(metric: Metric) -> Result { + match metric { + Metric::Counter(counter) => Ok(counter), + _ => Err(()), + } + } +} + +// +// CounterGuard impls +// + +impl CounterGuard { + pub fn update(&mut self, new_counter: &Counter) { + if new_counter != &self.counter { + self.counter.dec(self.value); + new_counter.inc(self.value); + self.counter = new_counter.clone(); + } + } +} + +impl Drop for CounterGuard { + fn drop(&mut self) { + self.counter.dec(self.value); + } +} + +// +// Gauge impls +// + +impl Gauge { + pub fn update(&self, value: impl num_traits::AsPrimitive) { + let value = value.as_(); + self.value.store(value.to_bits(), Ordering::SeqCst); + } + pub fn value(&self) -> f64 { + f64::from_bits(self.value.load(Ordering::SeqCst)) + } +} + +impl TryFrom for Gauge { + type Error = (); + fn try_from(metric: Metric) -> Result { + match metric { + Metric::Gauge(gauge) => Ok(gauge), + _ => Err(()), + } + } +} + +// +// Meter impls +// + +impl Meter { + pub fn mark(&self) { + self.inc(1); + } + + pub fn inc(&self, value: u64) { + self.tick_to(self.shared.start_time.elapsed()); + self.shared.count.fetch_add(value, Ordering::SeqCst); + self.shared.tick_total.fetch_add(value, Ordering::SeqCst); + } + + pub fn set(&self, new_count: u64) { + self.tick_to(self.shared.start_time.elapsed()); + let old_count = self.shared.count.swap(new_count, Ordering::SeqCst); + if let Some(count_diff) = new_count.checked_sub(old_count) { + if old_count != 0 && count_diff != 0 { + self.shared.tick_total.fetch_add(count_diff, Ordering::SeqCst); + } + } + } + + pub fn tick(&self, now: Instant) -> Duration { + let elapsed = now.duration_since(self.shared.start_time); + self.tick_to(elapsed); + elapsed + } + + pub fn count(&self) -> u64 { + self.shared.count.load(Ordering::SeqCst) + } + + pub fn m1_rate(&self) -> f64 { + self.shared.m1_rate.rate() + } + + pub fn m5_rate(&self) -> f64 { + self.shared.m5_rate.rate() + } + + pub fn m15_rate(&self) -> f64 { + self.shared.m15_rate.rate() + } + + fn tick_to(&self, current_tick: Duration) { + let current_tick = current_tick.as_secs(); + let last_tick = self.shared.last_tick.load(Ordering::SeqCst); + let elapsed = current_tick.saturating_sub(last_tick); + let interval = METER_INTERVAL.as_secs(); + if elapsed > interval { + let ticks_elapsed = elapsed / interval; + let new_last_tick = current_tick.saturating_sub(elapsed % interval); + if self.shared.last_tick.compare_and_swap(last_tick, new_last_tick, Ordering::SeqCst) == last_tick { + let total = self.shared.tick_total.swap(0, Ordering::SeqCst); + let rate = (total as f64) / (METER_INTERVAL.as_secs() as f64); + self.shared.m1_rate.tick(rate, ticks_elapsed); + self.shared.m5_rate.tick(rate, ticks_elapsed); + self.shared.m15_rate.tick(rate, ticks_elapsed); + } + } + } +} + +impl TryFrom for Meter { + type Error = (); + fn try_from(metric: Metric) -> Result { + match metric { + Metric::Meter(meter) => Ok(meter), + _ => Err(()), + } + } +} + +impl Default for MeterShared { + fn default() -> Self { + Self { + start_time: Instant::now(), + last_tick: Default::default(), + tick_total: Default::default(), + count: Default::default(), + m1_rate: ExponentiallyWeightedMovingAverage::new(ExponentiallyWeightedMovingAverage::default_alpha(1.0)), + m5_rate: ExponentiallyWeightedMovingAverage::new(ExponentiallyWeightedMovingAverage::default_alpha(5.0)), + m15_rate: ExponentiallyWeightedMovingAverage::new(ExponentiallyWeightedMovingAverage::default_alpha(15.0)), + } + } +} + +// +// Histogram impls +// + +impl TryFrom for Histogram { + type Error = (); + fn try_from(metric: Metric) -> Result { + match metric { + Metric::Histogram(histogram) => Ok(histogram), + _ => Err(()), + } + } +} + +// +// Timer impls +// + +impl TryFrom for Timer { + type Error = (); + fn try_from(metric: Metric) -> Result { + match metric { + Metric::Timer(timer) => Ok(timer), + _ => Err(()), + } + } +} + +// +// ExponentiallyWeightedMovingAverage impls +// + +impl ExponentiallyWeightedMovingAverage { + pub fn new(alpha: f64) -> Self { + Self { + alpha, + average: AtomicU64::new(0.0f64.to_bits()), + } + } + + pub fn default_alpha(secs: f64) -> f64 { + 1.0f64 - (-5.0f64 / 60.0 / secs).exp() + } + + pub fn rate(&self) -> f64 { + f64::from_bits(self.average.load(Ordering::SeqCst)) + } + + pub fn tick(&self, rate: f64, ticks: u64) { + if ticks != 0 { + let old_average = f64::from_bits(self.average.load(Ordering::SeqCst)); + let mut new_average = old_average + (self.alpha * (rate - old_average)); + if ticks > 1 { + new_average *= (1.0f64 - self.alpha).powi((ticks - 1) as i32); + } + self.average.store(new_average.to_bits(), Ordering::SeqCst); + } + } +} + +// +// Timer impls +// + +impl Timer { + pub fn time(&self) -> TimerGuard<'_> { + TimerGuard { + timer: self, + start: Instant::now(), + } + } + + pub fn update(&self, value: Duration) { + if let Ok(integer_value) = i64::try_from(value.as_millis()) { + self.meter.mark(); + self.histogram.update(integer_value); + } + } + + pub fn meter(&self) -> &Meter { + &self.meter + } + pub fn histogram(&self) -> &Histogram { + &self.histogram + } +} + +// +// TimerGuard impls +// + +impl<'a> TimerGuard<'a> { + pub fn stop(self) { + drop(self); + } +} + +impl<'a> Drop for TimerGuard<'a> { + fn drop(&mut self) { + self.timer.update(self.start.elapsed()); + } +} + +// +// Histogram impls +// + +impl Histogram { + pub fn update(&self, value: i64) { + let mut sample_guard = match self.sample.try_lock() { + Ok(guard) => guard, + Err(TryLockError::Poisoned(poison)) => poison.into_inner(), + Err(TryLockError::WouldBlock) => return, + }; + sample_guard.update(value); + } + pub fn snapshot(&self) -> Snapshot { + let sample_guard = match self.sample.lock() { + Ok(guard) => guard, + Err(poison) => poison.into_inner(), + }; + sample_guard.snapshot() + } +} diff --git a/service/kbupd/src/metrics/mod.rs b/service/kbupd/src/metrics/mod.rs new file mode 100644 index 0000000..0b366a7 --- /dev/null +++ b/service/kbupd/src/metrics/mod.rs @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +mod json_reporter; +#[macro_use] +mod macros; +mod metrics; +mod registry; +mod reporter; + +pub use json_reporter::*; +pub use metrics::*; +pub use registry::*; +pub use reporter::*; + +use crate::constants; + +lazy_static::lazy_static! { + pub static ref METRICS: MetricRegistry = MetricRegistries::global().get_or_create(constants::METRICS_NAME); +} + +pub fn metric_name>(parts: impl IntoIterator + Clone) -> String { + let name_len = parts.clone().into_iter().map(|part| part.as_ref().len() + 1).sum(); + let mut name = String::with_capacity(name_len); + for part in parts { + if !name.is_empty() { + name.push('.'); + } + name.push_str(part.as_ref()); + } + name +} diff --git a/service/kbupd/src/metrics/registry.rs b/service/kbupd/src/metrics/registry.rs new file mode 100644 index 0000000..e38eb3e --- /dev/null +++ b/service/kbupd/src/metrics/registry.rs @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::collections::*; +use std::convert::*; +use std::sync::*; + +use log::{error}; + +use super::*; + +#[derive(Default)] +pub struct MetricRegistries { + registries: Mutex>, +} + +#[derive(Clone, Default)] +pub struct MetricRegistry { + metrics: Arc>>, +} + +// +// MetricRegistries impls +// + +lazy_static::lazy_static! { + static ref GLOBAL_METRIC_REGISTRIES: MetricRegistries = Default::default(); +} + +impl MetricRegistries { + pub fn global() -> &'static MetricRegistries { + &*GLOBAL_METRIC_REGISTRIES + } + + pub fn get_or_create(&self, name: &'static str) -> MetricRegistry { + let mut registries = match self.registries.lock() { + Ok(guard) => guard, + Err(poison) => poison.into_inner(), + }; + registries.entry(name).or_insert_with(MetricRegistry::default).clone() + } +} + +// +// MetricRegistry impls +// + +impl MetricRegistry { + pub fn metrics(&self) -> Vec<(String, Metric)> { + let metrics = match self.metrics.lock() { + Ok(guard) => guard, + Err(poison_error) => poison_error.into_inner(), + }; + metrics.iter().map(|(k,v)| (k.clone(), v.clone())).collect() + } + + pub fn metric(&self, name: &str) -> MetricTy + where MetricTy: TryFrom + Clone + Default, + Metric: From, + { + match self.get_or_insert_metric(name.to_string()) { + Ok(meter) => meter, + Err(_metric) => { + error!("tried to add meter with existing metric of different type: {}", name); + MetricTy::default() + } + } + } + + fn get_or_insert_metric(&self, name: String) -> Result + where MetricTy: TryFrom + Clone + Default, + Metric: From, + { + let mut metrics = match self.metrics.lock() { + Ok(guard) => guard, + Err(poison_error) => poison_error.into_inner(), + }; + let metric = metrics.entry(name).or_insert_with(|| Metric::from(MetricTy::default())); + match MetricTy::try_from(metric.clone()) { + Ok(metric) => Ok(metric), + Err(()) => Err(metric.clone()), + } + } +} diff --git a/service/kbupd/src/metrics/reporter.rs b/service/kbupd/src/metrics/reporter.rs new file mode 100644 index 0000000..f2f55b1 --- /dev/null +++ b/service/kbupd/src/metrics/reporter.rs @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::sync::*; +use std::thread; +use std::time::*; + +use super::*; +use crate::util::thread::{StopJoinHandle, StopState}; + +pub trait Reporter: Send { + fn report(&mut self, registry: &MetricRegistry); +} + +pub struct PeriodicReporter { + reporter: ReporterTy, + registry: MetricRegistry, + interval: Duration, + stop_state: Arc, +} + +impl PeriodicReporter +where ReporterTy: Reporter + 'static, +{ + pub fn new(reporter: ReporterTy, registry: MetricRegistry, interval: Duration) -> Self { + Self { + reporter, + registry, + interval, + stop_state: Default::default(), + } + } + + pub fn start(mut self) -> StopJoinHandle<()> { + let stop_state = self.stop_state.clone(); + let join_handle = thread::spawn(move || { + while self.stop_state.sleep_while_running(self.interval) { + self.reporter.report(&self.registry); + } + }); + StopJoinHandle::new(stop_state, join_handle) + } +} diff --git a/service/kbupd/src/mocks.rs b/service/kbupd/src/mocks.rs new file mode 100644 index 0000000..27e2aab --- /dev/null +++ b/service/kbupd/src/mocks.rs @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::cell::*; +use std::fmt; + +use bytes::{Bytes}; +use futures::future; +use futures::prelude::*; +use http::header::{HeaderValue}; +use rand::{Rng, RngCore, SeedableRng}; +use rand::distributions::{Distribution, Standard}; +use rand_chacha::{ChaChaRng}; + +thread_local! { + static RAND_STATE: RefCell = RefCell::new(ChaChaRng::from_seed([0; 32])); +} + +pub fn rand_array() -> T where T: AsMut<[u8]> + Default { + rand_bytes(T::default()) +} +pub fn rand_bytes(mut buf: T) -> T where T: AsMut<[u8]> { + read_rand(buf.as_mut()); + buf +} +pub fn rand() -> T where Standard: Distribution { + RAND_STATE.with(|rand| rand.borrow_mut().gen()) +} +pub fn read_rand(buf: &mut [u8]) { + RAND_STATE.with(|rand| rand.borrow_mut().fill_bytes(buf)); +} + +pub fn basic_auth(username: impl fmt::Display, password: impl fmt::Display) -> HeaderValue { + let auth = format!("{}:{}", username, password); + let value = format!("Basic {}", base64::encode(&auth)); + HeaderValue::from_str(&value).unwrap() +} + +pub struct AsyncPipe { + tx: futures::sync::mpsc::UnboundedSender, + rx: futures::sync::mpsc::UnboundedReceiver, + read_buf: Option, +} +impl AsyncPipe { + pub fn new() -> (Self, Self) { + let (tx1, rx1) = futures::sync::mpsc::unbounded(); + let (tx2, rx2) = futures::sync::mpsc::unbounded(); + (Self { tx: tx1, rx: rx2, read_buf: Default::default() }, + Self { tx: tx2, rx: rx1, read_buf: Default::default() }) + } +} +impl std::io::Read for AsyncPipe { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let mut read_buf = if let Some(read_buf) = self.read_buf.take() { + read_buf + } else { + match self.rx.poll() { + Ok(Async::Ready(Some(data))) => data, + Ok(Async::Ready(None)) => return Ok(0), + Ok(Async::NotReady) => return Err(std::io::ErrorKind::WouldBlock.into()), + Err(()) => return Err(std::io::ErrorKind::BrokenPipe.into()), + } + }; + let read_len = buf.len().min(read_buf.len()); + buf[..read_len].copy_from_slice(&read_buf.split_to(read_len)[..]); + if !read_buf.is_empty() { + self.read_buf = Some(read_buf); + } + Ok(read_len) + } +} +impl tokio::io::AsyncRead for AsyncPipe {} +impl std::io::Write for AsyncPipe { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + if buf.is_empty() { + Ok(0) + } else { + match self.tx.unbounded_send(buf.into()) { + Ok(()) => Ok(buf.len()), + Err(_) => Err(std::io::ErrorKind::BrokenPipe.into()), + } + } + } + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} +impl tokio::io::AsyncWrite for AsyncPipe { + fn shutdown(&mut self) -> Poll<(), std::io::Error> { + Ok(Async::Ready(())) + } +} + +pub struct AsyncPipeConnector { + tx: futures::sync::mpsc::UnboundedSender, +} + +pub struct AsyncPipeIncoming { + rx: futures::sync::mpsc::UnboundedReceiver, +} + +impl AsyncPipeConnector { + pub fn new() -> (Self, AsyncPipeIncoming) { + let (tx, rx) = futures::sync::mpsc::unbounded(); + (Self { tx }, + AsyncPipeIncoming { rx }) + } +} + +impl hyper::client::connect::Connect for AsyncPipeConnector { + type Transport = AsyncPipe; + type Error = failure::Error; + type Future = future::FutureResult<(Self::Transport, hyper::client::connect::Connected), Self::Error>; + fn connect(&self, _dst: hyper::client::connect::Destination) -> Self::Future { + let (pipe1, pipe2) = AsyncPipe::new(); + match self.tx.unbounded_send(pipe1) { + Ok(()) => Ok((pipe2, hyper::client::connect::Connected::new())).into_future(), + Err(_) => Err(failure::format_err!("async pipe connector closed")).into_future(), + } + } +} + +impl Stream for AsyncPipeIncoming { + type Item = AsyncPipe; + type Error = failure::Error; + fn poll(&mut self) -> Poll, Self::Error> { + match self.rx.poll() { + Ok(async_result) => Ok(async_result), + Err(()) => Ok(Async::Ready(None)), + } + } +} diff --git a/service/kbupd/src/peer/codec.rs b/service/kbupd/src/peer/codec.rs new file mode 100644 index 0000000..a42c799 --- /dev/null +++ b/service/kbupd/src/peer/codec.rs @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::sync::{Arc}; + +use bytes::{Buf, BufMut, BytesMut, IntoBuf}; +use prost::{Message}; + +use crate::protobufs::kbupd::*; + +pub struct PeerCodec; + +impl tokio_codec::Decoder for PeerCodec { + type Item = PeerConnectionMessage; + type Error = tokio::io::Error; + fn decode(&mut self, buffer: &mut BytesMut) -> Result, Self::Error> { + if buffer.len() < 4 { + return Ok(None); + } + + let frame_length = buffer[..].into_buf().get_u32_be() as usize; + let frame_remaining = frame_length.saturating_sub(buffer.len() - 4); + if frame_remaining != 0 { + buffer.reserve(frame_remaining + 4); + return Ok(None); + } + + buffer.advance(4); + let data = buffer.split_to(frame_length); + let message = PeerConnectionMessage::decode(&data)?; + + Ok(Some(message)) + } +} + +impl tokio_codec::Encoder for PeerCodec { + type Item = Arc; + type Error = tokio::io::Error; + fn encode(&mut self, message: Arc, output: &mut BytesMut) -> Result<(), Self::Error> { + let frame_len = match message.encoded_len() { + frame_len if frame_len > u32::max_value() as usize - 4 => { + return Err(tokio::io::Error::new(tokio::io::ErrorKind::InvalidInput, "peer message size limit")); + } + frame_len => frame_len as u32, + }; + output.reserve(4 + frame_len as usize); + output.put_u32_be(frame_len); + message.encode(output)?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use std::sync::{Arc}; + + use bytes::{BytesMut}; + use tokio_codec::{Decoder, Encoder}; + + use super::*; + + #[test] + fn test_decode() { + let mut buf = BytesMut::new(); + + assert!(PeerCodec.decode(&mut buf).unwrap().is_none()); + + buf.put_u32_be(0); + buf.put_u32_be(0); + assert_eq!(PeerCodec.decode(&mut buf).unwrap(), Some(Default::default())); + assert_eq!(PeerCodec.decode(&mut buf).unwrap(), Some(Default::default())); + + let message = PeerConnectionMessage { + inner: Some(peer_connection_message::Inner::Hello(Default::default())), + }; + + let encoded_len = (message.encoded_len() as u32).to_be_bytes(); + for byte in &encoded_len { + buf.put_u8(*byte); + assert!(PeerCodec.decode(&mut buf).unwrap().is_none()); + } + + let () = message.encode(&mut buf).unwrap(); + let last_byte_1 = buf[buf.len() - 2]; + let last_byte_2 = buf[buf.len() - 1]; + buf.truncate(buf.len() - 2); + assert!(PeerCodec.decode(&mut buf).unwrap().is_none()); + + buf.put_u8(last_byte_1); + assert!(PeerCodec.decode(&mut buf).unwrap().is_none()); + + buf.put_u8(last_byte_2); + buf.put_u32_be(0); + assert_eq!(PeerCodec.decode(&mut buf).unwrap(), Some(message)); + assert_eq!(PeerCodec.decode(&mut buf).unwrap(), Some(Default::default())); + } + + #[test] + fn test_encode() { + let mut output = BytesMut::new(); + + let () = PeerCodec.encode(Default::default(), &mut output).unwrap(); + assert_eq!(output[..], [0, 0, 0, 0]); + + let () = PeerCodec.encode(Default::default(), &mut output).unwrap(); + assert_eq!(&output[..], [0, 0, 0, 0, 0, 0, 0, 0]); + + output.clear(); + + let message = PeerConnectionMessage { + inner: Some(peer_connection_message::Inner::Hello(Default::default())), + }; + + let () = PeerCodec.encode(Arc::new(message), &mut output).unwrap(); + assert_eq!(output[..4], 4u32.to_be_bytes()); + assert_eq!(output.len(), 8); + } +} diff --git a/service/kbupd/src/peer/connection.rs b/service/kbupd/src/peer/connection.rs new file mode 100644 index 0000000..56bd408 --- /dev/null +++ b/service/kbupd/src/peer/connection.rs @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::io; +use std::net::{SocketAddr}; +use std::sync::{Arc}; +use std::time::{Duration, Instant}; + +use futures::future; +use futures::prelude::*; +use kbuptlsd::prelude::*; +use tokio::net::{TcpStream}; +use tokio::timer; +use tokio_codec::{Decoder, Framed}; +use try_future::{try_future}; + +use crate::*; +use crate::protobufs::kbupd::*; +use crate::tls::*; +use super::codec::*; + +pub const CONNECT_RETRY_INITIAL_DELAY: Duration = Duration::from_millis(200); +pub const CONNECT_RETRY_MAXIMUM_DELAY: Duration = Duration::from_secs(15); + +pub type PeerFramed = Framed; + +pub struct PeerConnection { + pub framed: PeerFramed, + pub sent_hello: bool, +} + +impl PeerConnection { + pub fn new(stream: TlsProxyStream) -> Self { + let framed = PeerCodec.framed(stream); + Self { + framed, + sent_hello: false, + } + } + + pub fn connect(address: &str, tls_client: TlsClient) -> impl Future { + let socket_addr = try_future!(util::to_socket_addr(address)); + let stream = TcpStream::connect(&socket_addr); + let framed = stream.and_then(move |stream: TcpStream| { + Self::connect_with_tcp_stream(stream, socket_addr, &tls_client) + }); + framed.into() + } + + pub fn connect_with_tcp_stream(stream: TcpStream, address: SocketAddr, tls_client: &TlsClient) -> io::Result { + let _ignore = stream.set_nodelay(true); + let tls_stream = tls_client.spawn(stream, address)?; + + Ok(PeerConnection::new(tls_stream)) + } + + pub fn send_hello(self, our_hello: PeerConnectionHello) -> impl Future { + let Self { framed, sent_hello: _ } = self; + + let framed = framed.send(Arc::new(PeerConnectionMessage { + inner: Some(peer_connection_message::Inner::Hello(our_hello)), + })); + let connection = framed.map(move |framed: PeerFramed| { + Self { framed, sent_hello: true } + }); + connection + } + + pub fn read_hello(self) -> impl Future { + let Self { framed, sent_hello } = self; + + let maybe_message = framed.into_future().map_err(|(error, _framed): (io::Error, PeerFramed)| error); + let connection = maybe_message.and_then(move |(maybe_message, framed): (Option, PeerFramed)| { + match maybe_message { + Some(PeerConnectionMessage { inner: Some(peer_connection_message::Inner::Hello(peer_hello)) }) => { + Ok((peer_hello, Self { framed, sent_hello })) + } + Some(message) => { + Err(io::Error::new(io::ErrorKind::InvalidData, format!("first message on peer connection not a hello: {:?}", message))) + } + None => { + Err(io::Error::new(io::ErrorKind::Other, "eof before connection hello")) + } + } + }); + connection + } +} + +pub struct ReconnectLooper where F: Send + Clone { + connect_fn: F, + max_delay: Duration, +} + +impl ReconnectLooper +where F: FnOnce() -> Fut + Send + Clone, + Fut: Future, +{ + pub fn new(connect_fn: F) -> Self { + Self { + connect_fn, + max_delay: CONNECT_RETRY_INITIAL_DELAY, + } + } + pub fn into_future(self) -> impl Future { + future::loop_fn(self, Self::connect) + } + fn connect(self) -> impl Future, Error = ()> { + let connect_result = (self.connect_fn.clone())(); + let loop_result = connect_result.then(|result: Result| { + match result { + Ok(result) => { + future::Either::A(Ok(future::Loop::Break(result)).into_future()) + } + Err(()) => { + let delay_timer = timer::Delay::new(Instant::now() + util::duration::random(self.max_delay)); + let loop_result = delay_timer.map(|()| { + future::Loop::Continue(Self { + max_delay: (self.max_delay * 2).min(CONNECT_RETRY_MAXIMUM_DELAY), + ..self + }) + }); + future::Either::B(loop_result) + } + } + }); + + loop_result.map_err(|error: timer::Error| { + error!("tokio timer error: {}", error); + }) + } +} diff --git a/service/kbupd/src/peer/discovery.rs b/service/kbupd/src/peer/discovery.rs new file mode 100644 index 0000000..9d97b8f --- /dev/null +++ b/service/kbupd/src/peer/discovery.rs @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::io; + +use futures::future; +use futures::future::{Loop}; +use futures::prelude::*; +use futures::stream; + +use crate::*; +use crate::protobufs::kbupd::*; +use crate::tls::*; +use super::connection::*; +use super::manager::*; + +#[must_use] +pub struct PartitionPeerDiscovery { + range: Option, + addresses: Vec, + connecting: Vec + Send>>, + connected: Vec, +} + +struct DiscoveredPeer { + address: String, + peer_hello: PeerConnectionHello, + connection: PeerConnection, +} + +impl PartitionPeerDiscovery { + pub fn new(range: Option, addresses: Vec, tls_client: &TlsClient) -> Self { + info!("discovering peers for partition with range {} and peers {}", util::OptionDisplay(range.as_ref()), util::ListDisplay(&addresses)); + + let mut connecting = Vec::new(); + for address in addresses.iter().cloned() { + let tls_client = tls_client.clone(); + let reconnect_looper = ReconnectLooper::new(move || { + let address_2 = address.clone(); + let connection = PeerConnection::connect(&address, tls_client); + let peer_hello = connection.and_then(|connection: PeerConnection| { + connection.read_hello() + }); + let peer_hello = peer_hello.map_err(move |error: io::Error| { + warn!("error connecting to peer at {}: {}", address_2, error); + }); + let discovered_peer = peer_hello.map(|(peer_hello, connection): (PeerConnectionHello, PeerConnection)| { + DiscoveredPeer { address, peer_hello, connection } + }); + discovered_peer + }); + + let connection: Box + Send> = Box::new(reconnect_looper.into_future()); + connecting.push(connection); + } + + Self { + range, + addresses, + connecting, + connected: Default::default(), + } + } + + pub fn discover(self) -> impl Future + Send + 'static { + future::loop_fn(self, Self::discover_loop) + } + + fn discover_loop(self) -> impl Future, Error = ()> + Send + 'static { + let Self { range, addresses, connecting, mut connected } = self; + + let connection = future::select_all(connecting); + let maybe_partition = connection.then(move |connection_result: Result<(DiscoveredPeer, usize, Vec<_>), _>| { + let (discovered_peer, _index, connecting) = match connection_result { + Ok(ok) => ok, + Err(_) => { + error!("peer discovery failed for partition: {}", util::ListDisplay(&addresses)); + return Err(()); + } + }; + + let peer_hello = discovered_peer.peer_hello.clone(); + let peer_address = discovered_peer.address.clone(); + + connected.push(discovered_peer); + + match peer_hello.partition { + Some(mut partition) => { + let node_ids = partition.node_ids.iter().map(|node_id: &Vec| util::ToHex(node_id)); + info!("discovered group {} from {} with range {} and nodes {}", + util::ToHex(&partition.group_id), + peer_address, + util::OptionDisplay(partition.range.as_ref()), + util::ListDisplay(node_ids)); + if partition.range != range { + warn!("group {} reported differing range {} than expected {}", + util::ToHex(&partition.group_id), + util::OptionDisplay(partition.range.as_ref()), + util::OptionDisplay(range.as_ref())); + partition.range = range.clone(); + } + Ok(Loop::Break((partition, Self { range, addresses, connecting, connected }))) + } + None => { + info!("discovered no group from {}", peer_address); + if !connecting.is_empty() { + Ok(Loop::Continue(Self { range, addresses, connecting, connected })) + } else { + error!("discovered no groups from all replicas!"); + Err(()) + } + } + } + }); + maybe_partition + } + + #[must_use] + pub fn finish(self, peer_manager_tx: PeerManagerSender) -> impl Future { + let connections_rest = stream::futures_unordered(self.connecting); + let connections = Box::new(connections_rest.select(stream::iter_ok(self.connected))); + + connections.for_each(move |discovered_peer: DiscoveredPeer| { + let _ignore = peer_manager_tx.cast(|peer_manager: &mut PeerManager| { + peer_manager.add_connection(Some(discovered_peer.address), discovered_peer.peer_hello, discovered_peer.connection); + }); + Ok(()) + }) + } +} diff --git a/service/kbupd/src/peer/listener.rs b/service/kbupd/src/peer/listener.rs new file mode 100644 index 0000000..03fe5f2 --- /dev/null +++ b/service/kbupd/src/peer/listener.rs @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::net::{ToSocketAddrs}; +use std::path::{PathBuf}; + +use futures::prelude::*; +use kbuptlsd::prelude::*; + +use crate::*; +use super::connection::*; +use super::manager::*; + +pub struct PeerListener { + manager_tx: PeerManagerSender, + server: TlsProxyListener, +} + +impl PeerListener { + pub fn new(bind_address: impl ToSocketAddrs, kbuptlsd_bin_path: PathBuf, max_connections: usize, tls_arguments: TlsProxyListenerArguments, manager_tx: PeerManagerSender) -> Result { + let server = TlsProxyListener::new(bind_address, kbuptlsd_bin_path, max_connections, tls_arguments)?; + Ok(Self { + manager_tx, + server, + }) + } + pub fn into_future(self) -> impl Future { + let manager_tx = self.manager_tx; + let connections = self.server.into_stream().map_err(|error: failure::Error| { + error!("error starting peer listener: {}", error); + }); + + connections.for_each(move |stream: TlsProxyStream| { + info!("accepted peer connection from: {}", stream.peer_addr()); + + let connection = PeerConnection::new(stream); + + manager_tx.cast(move |manager: &mut PeerManager| { + manager.accept_connection(connection) + }) + }) + } +} diff --git a/service/kbupd/src/peer/manager.rs b/service/kbupd/src/peer/manager.rs new file mode 100644 index 0000000..d9b3444 --- /dev/null +++ b/service/kbupd/src/peer/manager.rs @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::convert::{TryFrom}; +use std::collections::{hash_map, HashMap}; +use std::io; +use std::sync::{Arc, RwLock}; +use std::time::{Duration}; + +use futures::prelude::*; +use futures::sync::{oneshot}; +use tokio::prelude::*; + +use crate::*; +use crate::metrics::*; +use crate::protobufs::kbupd::*; +use crate::tls::*; +use super::connection::*; +use super::peer::*; + +const PEER_HELLO_TIMEOUT: Duration = Duration::from_secs(60 * 3); + +pub type PeerManagerSender = actor::Sender; + +pub struct PeerManager { + self_tx: PeerManagerSender, + enclave_name: Arc, + enclave_tx: EnclaveManagerSender, + node_id: NodeId, + peers: HashMap>, + source_peers: Vec, + our_hello: Arc>, + tls_client: TlsClient, +} + +lazy_static::lazy_static! { + static ref MESSAGES_SENT_COUNT_METER: Meter = METRICS.metric(&metric_name!("messages", "sent", "count")); + static ref MESSAGES_SENT_BYTES_METER: Meter = METRICS.metric(&metric_name!("messages", "sent", "bytes")); + pub static ref MESSAGES_RECEIVED_COUNT_METER: Meter = METRICS.metric(&metric_name!("messages", "received", "count")); + pub static ref MESSAGES_RECEIVED_BYTES_METER: Meter = METRICS.metric(&metric_name!("messages", "received", "bytes")); + pub static ref MESSAGES_PENDING_COUNT_COUNTER: Counter = METRICS.metric(&metric_name!("messages", "pending", "count")); + pub static ref MESSAGES_PENDING_BYTES_COUNTER: Counter = METRICS.metric(&metric_name!("messages", "pending", "bytes")); + pub static ref PEERS_CONNECTED_COUNTER: Counter = METRICS.metric(&metric_name!("peers", "connected")); + pub static ref PEERS_CONNECTING_COUNTER: Counter = METRICS.metric(&metric_name!("peers", "connecting")); + pub static ref PEERS_DISCONNECTED_COUNTER: Counter = METRICS.metric(&metric_name!("peers", "disconnected")); +} + +impl PeerManager { + pub fn new(self_tx: PeerManagerSender, + enclave_name: String, + enclave_tx: EnclaveManagerSender, + node_id: NodeId, + tls_client: TlsClient) + -> Self + { + let our_hello = Arc::new(RwLock::new(PeerConnectionHello { + node_id: node_id.to_vec(), + partition: None, + })); + Self { + self_tx, + enclave_name: enclave_name.into(), + enclave_tx, + node_id, + peers: Default::default(), + source_peers: Default::default(), + our_hello, + tls_client, + } + } + + pub fn discover_peers(&mut self, + addresses: Vec, + tls_client: &TlsClient, + reply_tx: oneshot::Sender, futures::Canceled>>) + { + let mut peers = Vec::new(); + for address in addresses { + info!("discovering peer at {}", &address); + let self_tx = self.self_tx.clone(); + let our_hello = self.connection_hello(); + let tls_client = tls_client.clone(); + let reconnect_looper = ReconnectLooper::new(move || -> Box + Send> { + let connection = PeerConnection::connect(&address, tls_client); + let sent_hello = connection.and_then(move |connection: PeerConnection| { + connection.send_hello(our_hello) + }); + let peer_hello = sent_hello.and_then(PeerConnection::read_hello); + let address_2 = address.clone(); + let peer_hello = peer_hello.map_err(move |error: io::Error| { + warn!("error connecting to peer at {}: {}", address_2, error); + }); + + let peer_node_id = peer_hello.and_then(move |(peer_hello, connection): (PeerConnectionHello, PeerConnection)| { + let peer_node_id = NodeId::try_from(&peer_hello.node_id[..]).map_err(drop); + let _ignore = self_tx.cast(move |peer_manager: &mut PeerManager| { + peer_manager.add_connection(Some(address), peer_hello, connection) + }); + peer_node_id + }); + Box::new(peer_node_id) + }); + peers.push(reconnect_looper.into_future()); + } + + let our_node_id = self.node_id; + let peer_node_ids = future::join_all(peers).map(move |peer_node_ids: Vec| { + let peer_node_ids = peer_node_ids.into_iter().filter(|peer_node_id| peer_node_id != &our_node_id); + peer_node_ids.collect::>() + }); + + let replied_future = peer_node_ids.then(|result: Result, ()>| { + let result = result.map_err(|()| futures::Canceled); + reply_tx.send(result).map_err(|_| ()) + }); + + tokio::spawn(replied_future); + } + + pub fn set_source_partition(&mut self, source_node_ids: Vec) { + self.source_peers = source_node_ids; + } + + pub fn set_partition_config(&mut self, partition: Option) { + let mut our_hello_guard = match self.our_hello.write() { + Ok(our_hello_guard) => our_hello_guard, + Err(poison_error) => poison_error.into_inner(), + }; + *our_hello_guard = PeerConnectionHello { + node_id: self.node_id.to_vec(), + partition: partition, + }; + } + + pub fn force_reconnect(&self, peer_node_id: NodeId, maybe_new_address: Option) { + if let Some(peer_tx) = self.peers.get(&peer_node_id) { + info!("forcibly reconnecting to peer {}", &peer_node_id); + let _ignore = peer_tx.cast(move |peer: &mut Peer| peer.force_reconnect(maybe_new_address)); + } + } + + pub fn disconnect(&self, peer_node_id: NodeId) { + if let Some(peer_tx) = self.peers.get(&peer_node_id) { + info!("disconnecting from peer {}", &peer_node_id); + let _ignore = peer_tx.cast(|peer: &mut Peer| peer.disconnect()); + } + } + + pub fn disconnect_permanently(&mut self, peer_node_id: NodeId) { + if let Some(_peer_tx) = self.peers.remove(&peer_node_id) { + info!("permanently disconnecting from peer {}", &peer_node_id); + } + } + + pub fn xfer_finished(&self) { + for source_node_id in &self.source_peers { + self.disconnect(*source_node_id); + } + } + + pub fn send_message(&mut self, message: SendMessageRequest) { + MESSAGES_SENT_COUNT_METER.mark(); + MESSAGES_SENT_BYTES_METER.inc(message.data.len() as u64); + + let SendMessageRequest { node_id: peer_node_id, data, syn, debug_msg } = message; + + let peer_node_id = match NodeId::try_from(&peer_node_id[..]) { + Ok(peer_node_id) => peer_node_id, + Err(_) => { + error!("dropping message to invalid node id: {}", util::ToHex(&peer_node_id)); + return; + } + }; + + let peer_tx = match self.peers.entry(peer_node_id) { + hash_map::Entry::Occupied(peer_entry) => { + peer_entry.into_mut() + } + hash_map::Entry::Vacant(peer_entry) => { + let (peer_tx, peer_rx) = actor::channel(); + let peer = Peer::new(peer_entry.key().clone().into(), None, self.our_hello.clone(), self.tls_client.clone(), self.enclave_tx.clone(), self.enclave_name.clone(), None, peer_rx); + tokio::spawn(peer.map_err(|never: util::Never| match never {})); + warn!("waiting for peer {} with unknown address to connect", peer_entry.key()); + peer_entry.insert(peer_tx) + } + }; + + let _ignore = peer_tx.cast(move |peer: &mut Peer| peer.send_message(data, syn, debug_msg)); + } + + pub fn accept_connection(&mut self, connection: PeerConnection) { + let self_tx = self.self_tx.clone(); + let peer_addr = connection.framed.get_ref().peer_addr(); + + let sent_hello = connection.send_hello(self.connection_hello()); + let peer_hello = sent_hello.and_then(PeerConnection::read_hello); + + let connection_started = peer_hello.map(move |(hello, connection): (PeerConnectionHello, PeerConnection)| { + let _ignore = self_tx.cast(move |peer_manager: &mut PeerManager| { + peer_manager.add_connection(None, hello, connection) + }); + }); + + let connection_started = connection_started.timeout(PEER_HELLO_TIMEOUT); + let connection_started = connection_started.map_err(move |error: tokio::timer::timeout::Error| { + if error.is_elapsed() { + info!("timeout accepting connection from {}", peer_addr); + } else { + warn!("error accepting connection from {}: {}", peer_addr, error); + } + }); + + tokio::spawn(connection_started); + } + + pub fn add_connection(&mut self, + maybe_address: Option, + peer_hello: PeerConnectionHello, + connection: PeerConnection) + { + let peer_node_id = match NodeId::try_from(&peer_hello.node_id[..]) { + Ok(peer_node_id) if peer_node_id != self.node_id => peer_node_id, + Ok(_) => return, + Err(_) => { + error!("invalid node id from peer: {}", util::ToHex(&peer_hello.node_id)); + return; + } + }; + if let Some(peer_address) = &maybe_address { + info!("connected to peer {} at {}", &peer_node_id, peer_address); + } else { + let peer_socket_addr = connection.framed.get_ref().peer_addr(); + info!("accepted connection from peer {} at {}", &peer_node_id, peer_socket_addr); + } + + match self.peers.entry(peer_node_id) { + hash_map::Entry::Occupied(peer_entry) => { + let _ignore = peer_entry.get().cast(|peer: &mut Peer| peer.accept_connection(maybe_address, connection)); + } + hash_map::Entry::Vacant(peer_entry) => { + let (peer_tx, peer_rx) = actor::channel(); + let peer = Peer::new(peer_entry.key().clone().into(), maybe_address, self.our_hello.clone(), self.tls_client.clone(), self.enclave_tx.clone(), self.enclave_name.clone(), Some(connection), peer_rx); + tokio::spawn(peer.map_err(|never: util::Never| match never {})); + peer_entry.insert(peer_tx); + } + } + } + + fn connection_hello(&self) -> PeerConnectionHello { + let our_hello_guard = match self.our_hello.read() { + Ok(our_hello_guard) => our_hello_guard, + Err(poison_error) => poison_error.into_inner(), + }; + our_hello_guard.clone() + } +} diff --git a/service/kbupd/src/peer/mod.rs b/service/kbupd/src/peer/mod.rs new file mode 100644 index 0000000..6a1e7ff --- /dev/null +++ b/service/kbupd/src/peer/mod.rs @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +mod codec; +mod connection; +pub mod discovery; +pub mod listener; +pub mod manager; +mod peer; diff --git a/service/kbupd/src/peer/peer.rs b/service/kbupd/src/peer/peer.rs new file mode 100644 index 0000000..8d3ed66 --- /dev/null +++ b/service/kbupd/src/peer/peer.rs @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::collections::{VecDeque}; +use std::io; +use std::net::{SocketAddr}; +use std::sync::{Arc, RwLock}; +use std::time::{Duration, Instant}; + +use futures::prelude::*; +use tokio::net::tcp::{TcpStream, ConnectFuture}; +use tokio::timer; + +use crate::*; +use crate::metrics::*; +use crate::protobufs::kbupd::*; +use crate::tls::*; +use super::connection::*; +use super::manager::*; + +const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(30); +const INACTIVITY_TIMEOUT: Duration = Duration::from_secs(60 * 5); + +pub struct Peer { + node_id: NodeId, + address: Option, + our_hello: Arc>, + tls_client: TlsClient, + enclave_tx: EnclaveManagerSender, + enclave_name: Arc, + state: PeerState, + state_counter: CounterGuard, + rx: actor::Receiver, + pending_outgoing: VecDeque, + next_outgoing_id: u64, + next_incoming_id: u64, +} + +enum PeerState { + Disconnected, + Waiting { + delay: timer::Delay, + max_delay: Duration, + }, + Connecting { + connect: ConnectFuture, + address: SocketAddr, + max_delay: Duration, + }, + Connected(PeerConnectedState), +} + +struct PeerConnectedState { + framed: PeerFramed, + queue: VecDeque>, + keepalive: Option, + timeout: timer::Delay, +} + +struct OutgoingPeerMessage { + id: u64, + data: Arc, +} + +// +// Peer impls +// + +impl Future for Peer { + type Item = (); + type Error = util::Never; + + fn poll(&mut self) -> Result, util::Never> { + loop { + match self.inner_poll() { + Ok(Async::Ready(())) => (), + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(()) => return Ok(Async::Ready(())), + } + } + } +} + +impl Peer { + pub fn new(node_id: NodeId, + address: Option, + our_hello: Arc>, + tls_client: TlsClient, + enclave_tx: EnclaveManagerSender, + enclave_name: Arc, + connection: Option, + rx: actor::Receiver) + -> Self + { + let mut new_self = Self { + node_id, + address, + our_hello, + tls_client, + enclave_tx, + enclave_name, + state: PeerState::Disconnected, + state_counter: PEERS_DISCONNECTED_COUNTER.guard(1), + rx, + pending_outgoing: Default::default(), + next_outgoing_id: Default::default(), + next_incoming_id: Default::default(), + }; + if let Some(connection) = connection { + new_self.accept_connection(None, connection); + } + new_self + } + + pub fn accept_connection(&mut self, maybe_connect_address: Option, connection: PeerConnection) { + if let Some(connect_address) = maybe_connect_address { + self.address = Some(connect_address); + } + let replace_connection = if let PeerState::Connected(_) = &self.state { + // XXX this doesn't work with blackholed connection on non-initiator (when self.address is none) + self.address.is_none() || self.node_id[..] > self.connection_hello().node_id[..] + } else { + true + }; + if replace_connection { + self.state = self.connected(connection); + } + } + + pub fn force_reconnect(&mut self, maybe_new_address: Option) { + self.state = PeerState::Disconnected; + if let Some(new_address) = maybe_new_address { + self.address = Some(new_address); + } + } + + pub fn disconnect(&mut self) { + self.state = PeerState::Disconnected; + self.address = None; + } + + pub fn send_message(&mut self, data: Vec, syn: bool, debug_msg: Option>) { + MESSAGES_PENDING_COUNT_COUNTER.inc(1); + MESSAGES_PENDING_BYTES_COUNTER.inc(data.len() as u64); + + let id = self.next_outgoing_id; + + if syn { + debug!("sending connect request {} to peer {}", id, &self.node_id); + } else if let Some(debug_msg) = &debug_msg { + debug!("sending message {} to peer {}: {}", id, &self.node_id, String::from_utf8_lossy(debug_msg)); + } else { + debug!("sending message {} to peer {}", id, &self.node_id); + } + + let message = OutgoingPeerMessage { + id, + data: Arc::new(PeerConnectionMessage { + inner: Some(peer_connection_message::Inner::Data(PeerConnectionData { + id, + data, + syn, + })) + }), + }; + + if let PeerState::Connected(connected_state) = &mut self.state { + connected_state.enqueue(message.data.clone()); + } + self.pending_outgoing.push_back(message); + self.next_outgoing_id = self.next_outgoing_id.checked_add(1).expect("peer message id limit"); + } + + fn connection_hello(&self) -> PeerConnectionHello { + let our_hello_guard = match self.our_hello.read() { + Ok(our_hello_guard) => our_hello_guard, + Err(poison_error) => poison_error.into_inner(), + }; + our_hello_guard.clone() + } + + fn inner_poll(&mut self) -> Result, ()> { + match self.rx.poll() { + Ok(Async::Ready(Some(fun))) => { + fun(self); + return Ok(Async::Ready(())); + } + Ok(Async::NotReady) => (), + Ok(Async::Ready(None)) | Err(()) => { + warn!("dropping state for peer {}", &self.node_id); + return Err(()); + } + } + + match self.step() { + Async::Ready(maybe_new_state) => { + if let Some(new_state) = maybe_new_state { + self.state = new_state; + } + return Ok(Async::Ready(())); + } + Async::NotReady => (), + } + + let state_counter = match &self.state { + PeerState::Disconnected => &*PEERS_DISCONNECTED_COUNTER, + PeerState::Waiting { .. } | + PeerState::Connecting { .. } => &*PEERS_CONNECTING_COUNTER, + PeerState::Connected(_) => &*PEERS_CONNECTED_COUNTER, + }; + self.state_counter.update(state_counter); + + Ok(Async::NotReady) + } + + fn step(&mut self) -> Async> { + match &mut self.state { + PeerState::Disconnected => { + self.connect(CONNECT_RETRY_INITIAL_DELAY) + } + PeerState::Waiting { delay, max_delay } => { + let max_delay = *max_delay; + match delay.poll() { + Ok(Async::Ready(_)) => self.connect(max_delay), + Ok(Async::NotReady) => Async::NotReady, + Err(timer_error) => { + error!("tokio timer error in reconnect: {}", timer_error); + self.connect(max_delay) + } + } + } + PeerState::Connecting { connect, address, max_delay } => { + let max_delay = *max_delay; + match connect.poll() { + Ok(Async::Ready(stream)) => { + match PeerConnection::connect_with_tcp_stream(stream, *address, &self.tls_client) { + Ok(connection) => { + let new_state = self.connected(connection); + Async::Ready(Some(new_state)) + } + Err(connect_error) => { + warn!("error connecting to peer at {}: {}", util::OptionDisplay(self.address.as_ref()), connect_error); + let new_state = self.waiting(max_delay); + Async::Ready(Some(new_state)) + } + } + } + Ok(Async::NotReady) => Async::NotReady, + Err(connect_error) => { + warn!("error connecting to peer at {}: {}", util::OptionDisplay(self.address.as_ref()), connect_error); + let new_state = self.waiting(max_delay); + Async::Ready(Some(new_state)) + } + } + } + PeerState::Connected(connected_state) => { + match connected_state.flush_queue() { + Ok(Async::Ready(())) => return Async::Ready(None), + Ok(Async::NotReady) => (), + Err(write_error) => { + warn!("error writing to socket for peer {}: {}", &self.node_id, write_error); + return Async::Ready(Some(PeerState::Disconnected)); + } + } + + match connected_state.framed.poll() { + Ok(Async::Ready(None)) => { + info!("connection closed to peer {}", &self.node_id); + return Async::Ready(Some(PeerState::Disconnected)); + } + Ok(Async::Ready(Some(message))) => { + connected_state.reschedule_timeout(); + let maybe_new_state = self.handle_incoming_message(message); + return Async::Ready(maybe_new_state); + } + Ok(Async::NotReady) => (), + Err(read_error) => { + warn!("error reading from socket for peer {}: {}", &self.node_id, read_error); + return Async::Ready(Some(PeerState::Disconnected)); + } + } + + match connected_state.timeout.poll() { + Ok(Async::Ready(())) => { + warn!("connection timed out for peer {}", &self.node_id); + return Async::Ready(Some(PeerState::Disconnected)); + } + Ok(Async::NotReady) => (), + Err(timer_error) => { + error!("tokio timer error with timeout timer for peer {}: {}", &self.node_id, timer_error); + connected_state.reschedule_timeout(); + } + } + + if let Some(keepalive) = &mut connected_state.keepalive { + match keepalive.poll() { + Ok(Async::Ready(())) => { + let keepalive = Arc::new(PeerConnectionMessage { + inner: None, + }); + connected_state.enqueue(keepalive); + return Async::Ready(None); + } + Ok(Async::NotReady) => (), + Err(timer_error) => { + error!("tokio timer error with keepalive timer for peer {}: {}", &self.node_id, timer_error); + connected_state.keepalive = None; + } + } + } + + Async::NotReady + } + } + } + + fn connect(&self, max_delay: Duration) -> Async> { + if let Some(address) = &self.address { + info!("connecting to peer {} at {}", &self.node_id, address); + match util::to_socket_addr(address) { + Ok(socket_addr) => { + Async::Ready(Some(PeerState::Connecting { + connect: TcpStream::connect(&socket_addr), + address: socket_addr, + max_delay, + })) + } + Err(error) => { + error!("invalid address to connect to peer {}: {}", &self.node_id, error); + let new_state = self.waiting(max_delay); + Async::Ready(Some(new_state)) + } + } + } else { + debug!("no address to connect to peer {}!", &self.node_id); + Async::NotReady + } + } + + fn waiting(&self, max_delay: Duration) -> PeerState { + let delay_duration = util::duration::random(max_delay); + let delay = timer::Delay::new(Instant::now() + max_delay + delay_duration); + let max_delay = (max_delay * 2).min(CONNECT_RETRY_MAXIMUM_DELAY); + + PeerState::Waiting { delay, max_delay } + } + + fn connected(&self, connection: PeerConnection) -> PeerState { + let mut connected_state = PeerConnectedState::new(connection.framed, 1 + self.pending_outgoing.len()); + if !connection.sent_hello { + connected_state.enqueue(Arc::new(PeerConnectionMessage { + inner: Some(peer_connection_message::Inner::Hello(self.connection_hello())), + })); + } + match self.pending_outgoing.len() { + 0 => (), + pending_outgoing_len => { + info!("resending {} pending outgoing messages to peer {}", pending_outgoing_len, &self.node_id); + } + } + for pending_message in &self.pending_outgoing { + connected_state.enqueue(pending_message.data.clone()); + } + PeerState::Connected(connected_state) + } + + fn handle_incoming_message(&mut self, message: PeerConnectionMessage) -> Option { + match message.inner { + Some(peer_connection_message::Inner::Hello(hello)) => { + if hello.node_id[..] != self.node_id[..] { + warn!("reconnected to node {} at {} but found node {} instead!", + &self.node_id, util::OptionDisplay(self.address.as_ref()), &util::ToHex(&hello.node_id)); + self.address = None; + Some(PeerState::Disconnected) + } else { + None + } + } + Some(peer_connection_message::Inner::DataAck(ack)) => { + self.handle_incoming_data_ack(ack); + None + } + Some(peer_connection_message::Inner::Data(message)) => { + MESSAGES_RECEIVED_COUNT_METER.mark(); + MESSAGES_RECEIVED_BYTES_METER.inc(message.data.len() as u64); + self.handle_incoming_data(message); + None + } + None => { + // keepalive + None + } + } + } + + fn handle_incoming_data(&mut self, message: PeerConnectionData) { + let message_id = message.id; + if message_id == self.next_incoming_id { + debug!("received message {} from peer {}", message_id, &self.node_id); + match self.send_incoming(message) { + Ok(()) => self.next_incoming_id = self.next_incoming_id.checked_add(1).expect("peer message id limit"), + Err(()) => { + warn!("error sending incoming message to enclave for peer {}", &self.node_id); + } + } + } + + if message_id < self.next_incoming_id { + if let PeerState::Connected(connected_state) = &mut self.state { + let ack = Arc::new(PeerConnectionMessage { + inner: Some(peer_connection_message::Inner::DataAck(PeerConnectionDataAck { + id: message_id, + })), + }); + connected_state.enqueue(ack); + } + } else { + warn!("dropping message {} (expected {}) from peer {}", message_id, self.next_incoming_id, &self.node_id); + } + } + + fn handle_incoming_data_ack(&mut self, ack: PeerConnectionDataAck) { + let mut acked_count: u64 = 0; + let mut acked_bytes: u64 = 0; + while let Some(pending_message) = self.pending_outgoing.front() { + if pending_message.id <= ack.id { + if let Some(pending_message) = self.pending_outgoing.pop_front() { + acked_count += 1; + if let Some(peer_connection_message::Inner::Data(pending_message_data)) = &pending_message.data.inner { + acked_bytes += pending_message_data.data.len() as u64; + } + } + } else { + break; + } + } + MESSAGES_PENDING_COUNT_COUNTER.dec(acked_count); + MESSAGES_PENDING_BYTES_COUNTER.dec(acked_bytes); + } + + fn send_incoming(&self, message: PeerConnectionData) -> Result<(), ()> { + let peer_node_id = self.node_id.to_vec(); + let enclave_name = self.enclave_name.clone(); + self.enclave_tx.cast(|enclave_manager: &mut EnclaveManager| { + enclave_manager.untrusted_message(enclave_name, UntrustedMessage { + inner: Some(untrusted_message::Inner::NewMessageSignal(NewMessageSignal { + node_id: peer_node_id, + data: message.data, + syn: message.syn, + })), + }) + }).map_err(|_| ()) + } +} + +impl Drop for Peer { + fn drop(&mut self) { + let mut pending_bytes: u64 = 0; + for pending_message in &self.pending_outgoing { + if let Some(peer_connection_message::Inner::Data(pending_message_data)) = &pending_message.data.inner { + pending_bytes += pending_message_data.data.len() as u64; + } + } + MESSAGES_PENDING_COUNT_COUNTER.dec(self.pending_outgoing.len() as u64); + MESSAGES_PENDING_BYTES_COUNTER.dec(pending_bytes); + } +} + +// +// PeerConnectedState impls +// + +impl PeerConnectedState { + fn new(framed: PeerFramed, queue_capacity: usize) -> Self { + let now = Instant::now(); + let timeout = timer::Delay::new(now + INACTIVITY_TIMEOUT); + let keepalive = Some(timer::Delay::new(now + KEEPALIVE_INTERVAL)); + let queue = VecDeque::with_capacity(queue_capacity); + Self { framed, queue, keepalive, timeout } + } + + fn enqueue(&mut self, message: Arc) { + self.queue.push_back(message); + self.keepalive = None; + } + + fn flush_queue(&mut self) -> Result, io::Error> { + let () = futures::try_ready!(self.framed.poll_complete()); + + let start_send_result = if let Some(message) = self.queue.pop_front() { + match self.framed.start_send(message)? { + AsyncSink::Ready => + Async::Ready(()), + AsyncSink::NotReady(message) => { + self.queue.push_front(message); + Async::NotReady + } + } + } else { + Async::NotReady + }; + self.schedule_keepalive(); + Ok(start_send_result) + } + + fn schedule_keepalive(&mut self) { + if self.queue.is_empty() { + if self.keepalive.is_none() { + self.keepalive = Some(timer::Delay::new(Instant::now() + KEEPALIVE_INTERVAL)); + } + } else { + self.keepalive = None; + } + } + + fn reschedule_timeout(&mut self) { + self.timeout = timer::Delay::new(Instant::now() + INACTIVITY_TIMEOUT); + } +} diff --git a/service/kbupd/src/protobufs.rs b/service/kbupd/src/protobufs.rs new file mode 100644 index 0000000..6d9f13f --- /dev/null +++ b/service/kbupd/src/protobufs.rs @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod kbupd { + use kbupd_client; + include!(concat!(env!("OUT_DIR"), "/protobufs.kbupd.rs")); +} diff --git a/service/kbupd/src/protobufs_impl/kbupd.rs b/service/kbupd/src/protobufs_impl/kbupd.rs new file mode 100644 index 0000000..b38d8ae --- /dev/null +++ b/service/kbupd/src/protobufs_impl/kbupd.rs @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::protobufs::kbupd::*; +use crate::util::{DisplayAsDebug, ListDisplay, OptionDisplay, ToHex}; + +impl std::fmt::Display for EnclaveStatus { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let EnclaveStatus { + name, + node_id, + config, + status, + } = self; + let mut debug = fmt.debug_struct("EnclaveStatus"); + debug.field("name", name); + debug.field("node_id", &ToHex(node_id)); + + match config { + Some(enclave_status::Config::ReplicaConfig(replica_config)) => { + debug.field("replica_config", replica_config); + } + Some(enclave_status::Config::FrontendConfig(frontend_config)) => { + debug.field("frontend_config", frontend_config); + } + None => (), + } + + match status { + Some(enclave_status::Status::ReplicaStatus(replica_status)) => { + debug.field("replica_status", &DisplayAsDebug(replica_status)); + } + Some(enclave_status::Status::FrontendStatus(frontend_status)) => { + debug.field("frontend_status", &DisplayAsDebug(frontend_status)); + } + None => (), + } + + debug.finish() + } +} + +impl std::fmt::Display for EnclaveMemoryStatus { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let EnclaveMemoryStatus { + footprint_bytes, + used_bytes, + free_chunks, + } = self; + let mut debug = fmt.debug_struct("EnclaveFrontendStatus"); + debug.field("footprint_bytes", footprint_bytes); + debug.field("used_bytes", used_bytes); + debug.field("free_chunks", free_chunks); + debug.finish() + } +} + +impl std::fmt::Display for EnclaveFrontendStatus { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let EnclaveFrontendStatus { + memory_status, + partitions, + ranges, + } = self; + let mut debug = fmt.debug_struct("EnclaveFrontendStatus"); + if let Some(memory_status) = memory_status { + debug.field("memory_status", &DisplayAsDebug(memory_status)); + } + debug.field("partitions", &ListDisplay(partitions)); + debug.field("ranges", &ListDisplay(ranges)); + debug.finish() + } +} + +impl std::fmt::Display for EnclaveFrontendPartitionStatus { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let EnclaveFrontendPartitionStatus { + group_id, + nodes, + } = self; + let mut debug = fmt.debug_struct("EnclaveFrontendPartitionStatus"); + debug.field("group_id", &ToHex(group_id)); + debug.field("nodes", &ListDisplay(nodes)); + debug.finish() + } +} + +impl std::fmt::Display for EnclaveFrontendRangeStatus { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let EnclaveFrontendRangeStatus { + range, + group_id, + } = self; + write!(fmt, "{} => {}", range, &ToHex(group_id)) + } +} + +impl std::fmt::Display for EnclaveReplicaStatus { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let EnclaveReplicaStatus { + memory_status, + partition, + } = self; + + let mut debug = fmt.debug_struct("EnclaveReplicaStatus"); + if let Some(memory_status) = memory_status { + debug.field("memory_status", &DisplayAsDebug(memory_status)); + } + if let Some(partition) = partition { + debug.field("partition", &DisplayAsDebug(partition)); + } + debug.finish() + } +} + +impl std::fmt::Display for EnclaveReplicaPartitionStatus { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let EnclaveReplicaPartitionStatus { + group_id, + service_id, + range, + peers, + min_attestation, + is_leader, + current_term, + prev_log_index, + last_applied_index, + commit_index, + last_log_index, + last_log_term, + log_data_length, + backup_count, + xfer_status, + } = self; + let mut debug = fmt.debug_struct("EnclaveReplicaPartitionStatus"); + debug.field("group_id", &ToHex(group_id)); + debug.field("service_id", &OptionDisplay(service_id.as_ref().map(ToHex::new))); + debug.field("range", &OptionDisplay(range.as_ref())); + debug.field("peers", &ListDisplay(peers)); + debug.field("min_attestation", &DisplayAsDebug(min_attestation)); + debug.field("is_leader", is_leader); + debug.field("current_term", current_term); + debug.field("prev_log_index", prev_log_index); + debug.field("last_applied_index", last_applied_index); + debug.field("commit_index", commit_index); + debug.field("last_log_index", last_log_index); + debug.field("last_log_term", last_log_term); + debug.field("log_data_length", log_data_length); + debug.field("backup_count", backup_count); + match xfer_status { + Some(enclave_replica_partition_status::XferStatus::IncomingXferStatus(incoming_xfer_status)) => { + debug.field("incoming_xfer_status", &DisplayAsDebug(incoming_xfer_status)); + } + Some(enclave_replica_partition_status::XferStatus::OutgoingXferStatus(outgoing_xfer_status)) => { + debug.field("outgoing_xfer_status", &DisplayAsDebug(outgoing_xfer_status)); + } + None => (), + } + debug.finish() + } +} + +impl std::fmt::Display for EnclavePeerStatus { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let EnclavePeerStatus { + node_id, + attestation, + replication_status, + is_leader, + inflight_requests, + unsent_requests, + } = self; + let mut debug = fmt.debug_struct("EnclavePeerStatus"); + debug.field("node_id", &ToHex(node_id)); + debug.field("attestation", &OptionDisplay(attestation.as_ref())); + if let Some(replication_status) = replication_status { + debug.field("replication_status", &DisplayAsDebug(replication_status)); + } + debug.field("is_leader", is_leader); + debug.field("inflight_requests", inflight_requests); + debug.field("unsent_requests", unsent_requests); + + debug.finish() + } +} + +impl std::fmt::Display for EnclavePeerReplicationStatus { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let EnclavePeerReplicationStatus { + next_index, + match_index, + inflight_index, + probing, + } = self; + let mut debug = fmt.debug_struct("EnclavePeerReplicationStatus"); + debug.field("next_index", next_index); + debug.field("match_index", match_index); + debug.field("inflight_index", &OptionDisplay(inflight_index.as_ref())); + debug.field("probing", probing); + debug.finish() + } +} + +impl std::fmt::Display for EnclaveIncomingXferStatus { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let EnclaveIncomingXferStatus { + desired_range, + nodes, + } = self; + let mut debug = fmt.debug_struct("EnclaveIncomingXferStatus"); + debug.field("desired_range", &DisplayAsDebug(desired_range)); + debug.field("nodes", &ListDisplay(nodes)); + debug.finish() + } +} + +impl std::fmt::Display for EnclaveOutgoingXferStatus { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let EnclaveOutgoingXferStatus { + group_id, + full_xfer_range, + current_chunk_range, + paused, + min_attestation, + nodes, + } = self; + let mut debug = fmt.debug_struct("EnclaveOutgoingXferStatus"); + debug.field("group_id", &ToHex(group_id)); + debug.field("full_xfer_range", &DisplayAsDebug(full_xfer_range)); + debug.field("current_chunk_range", &OptionDisplay(current_chunk_range.as_ref())); + debug.field("paused", paused); + debug.field("min_attestation", &OptionDisplay(min_attestation.as_ref())); + debug.field("nodes", &nodes.iter().map(DisplayAsDebug).collect::>()); + debug.finish() + } +} + +impl std::fmt::Display for PartitionKeyRangePb { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(fmt, "{}-{}", ToHex(&self.first.id), ToHex(&self.last.id)) + } +} + +impl std::fmt::Display for AttestationParameters { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let AttestationParameters { + unix_timestamp_seconds, + } = self; + let timestamp: chrono::DateTime = + chrono::DateTime::from(std::time::UNIX_EPOCH + std::time::Duration::from_secs(*unix_timestamp_seconds)); + write!(fmt, "{}", timestamp.to_rfc3339()) + } +} diff --git a/service/kbupd/src/protobufs_impl/mod.rs b/service/kbupd/src/protobufs_impl/mod.rs new file mode 100644 index 0000000..9610397 --- /dev/null +++ b/service/kbupd/src/protobufs_impl/mod.rs @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +mod kbupd; diff --git a/service/kbupd/src/replica/mod.rs b/service/kbupd/src/replica/mod.rs new file mode 100644 index 0000000..ea609aa --- /dev/null +++ b/service/kbupd/src/replica/mod.rs @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::path::{Path}; +use std::time::{Duration}; +use std::thread; +use std::sync::{Arc}; + +use failure::{ResultExt}; +use futures::prelude::*; +use hyper::client::connect::{HttpConnector}; +use kbupd_config::{ReplicaConfig}; +use kbupd_config::metrics::*; +use kbuptlsd::prelude::*; +use nix::sys::signal; +use nix::sys::signal::Signal::*; + +use crate::*; +use crate::enclave::attestation_manager::{AttestationManager}; +use crate::enclave::timer_tick::{EnclaveTimerTickTask}; +use crate::enclave::status_refresh::{EnclaveStatusRefreshTask}; +use crate::enclave::revocation_list_refresh::{RevocationListRefreshTask}; +use crate::metrics::{JsonReporter, METRICS, PeriodicReporter}; +use crate::peer::listener::*; +use crate::peer::manager::*; +use crate::protobufs::kbupd::*; +use crate::tls::*; + +const ELECTION_TIMEOUT_TICKS: u32 = 10; + +const MIN_CONNECT_TIMEOUT: Duration = Duration::from_secs(15); +const MAX_CONNECT_TIMEOUT: Duration = Duration::from_secs(120); + +const DEFAULT_METRICS_INTERVAL: Duration = Duration::from_secs(60); +const ENCLAVE_STATUS_REFRESH_INTERVAL: Duration = Duration::from_secs(60); +const REVOCATION_LIST_REFRESH_INTERVAL: Duration = Duration::from_secs(600); + +const ENCLAVE_NAME: &str = ""; + +pub struct ReplicaService { + runtime: tokio::runtime::current_thread::Runtime, + enclave_thread_joiner: Box, Error = Box>>, +} + +#[derive(Clone)] +pub struct ReplicaCommandLineConfig<'a> { + pub enclave_directory: &'a Path, + pub config_directory: &'a Path, + pub kbuptlsd_bin_path: &'a Path, + pub full_hostname: Option<&'a str>, + pub exit_signals_enabled: bool, +} + +impl ReplicaService { + pub fn start(config: ReplicaConfig, + cmdline_config: ReplicaCommandLineConfig<'_>, + peer_tls_server_args: TlsProxyListenerArguments, + peer_tls_client_args: TlsClientProxyArguments) + -> Result { + let mut runtime = tokio::runtime::current_thread::Builder::new().build().context("error starting tokio runtime")?; + + let (peer_manager_tx, peer_manager_rx) = actor::channel(); + let (attestation_manager_tx, attestation_manager_rx) = actor::channel(); + + let enclave_manager_channel = EnclaveManagerChannel::new(); + let enclave_manager_tx = enclave_manager_channel.sender().clone(); + + let maybe_intel_client = if !config.attestation.disabled { + let intel_client_proxy = TlsClientProxySpawner::new(cmdline_config.kbuptlsd_bin_path.to_owned(), TlsClientProxyArguments::Config { + config_file: util::join_if_relative(cmdline_config.config_directory, &config.attestation.tlsConfigPath), + key_file: None, + }).context("error creating intel attestation tls client proxy")?; + Some(IntelClient::new(&config.attestation.host, intel_client_proxy).context("error creating intel attestation client")?) + } else { + None + }; + + let peer_tls_client = TlsClient::new(cmdline_config.kbuptlsd_bin_path.to_owned(), peer_tls_client_args).context("error creating peer tls client")?; + + let enclave_spid = config.attestation.spid; + let peer_listen_address = config.enclave.listenHostPort; + let tls_max_connections = config.enclave.maxConnections.max(128) as usize; + let enclave_mrenclave = config.enclave.mrenclave; + let enclave_debug = config.enclave.debug; + let enclave_filename = format!("{}.so", &enclave_mrenclave); + let enclave_path = cmdline_config.enclave_directory.join(&enclave_filename); + + let election_timeout_ticks = ELECTION_TIMEOUT_TICKS.max(config.enclave.electionHeartbeats); + let election_timeout = Duration::from_millis(config.enclave.electionTimeoutMs); + let timer_tick_interval = election_timeout / election_timeout_ticks; + + let enclave_replica_config = EnclaveReplicaConfig { + election_timeout_ticks, + heartbeat_timeout_ticks: election_timeout_ticks.checked_div(config.enclave.electionHeartbeats).unwrap_or(1), + min_connect_timeout_ticks: util::duration::as_ticks(MIN_CONNECT_TIMEOUT, timer_tick_interval), + max_connect_timeout_ticks: util::duration::as_ticks(MAX_CONNECT_TIMEOUT, timer_tick_interval), + attestation_expiry_ticks: util::duration::as_ticks(Duration::from_millis(config.enclave.attestationExpiryCommitIntervalMs), timer_tick_interval), + request_quote_ticks: util::duration::as_ticks(Duration::from_millis(config.enclave.attestationExpiryCommitIntervalMs), timer_tick_interval), + replication_chunk_size: config.enclave.replicationChunkSize, + transfer_chunk_size: config.enclave.transferChunkSize, + storage_page_cache_size: Default::default(), // unused + max_frontend_count: config.enclave.maxFrontendCount, + + raft_log_index_page_cache_size: 10, + }; + + info!("starting enclave with mrenclave {} with timer tick interval {}ms and {:#?}", + &enclave_mrenclave, timer_tick_interval.as_millis(), &enclave_replica_config); + + let start_replica_request = StartReplicaRequest { + config: enclave_replica_config, + }; + + let (enclave_join_tx, enclave_join_rx) = futures::sync::oneshot::channel::(); + + let (node_id_tx, node_id_rx) = std::sync::mpsc::channel(); + let enclave_peer_manager_tx = peer_manager_tx.clone(); + let enclave_thread = thread::spawn(move || -> Result<(), failure::Error> { + let mut enclave = Enclave::new(ENCLAVE_NAME.to_string(), &enclave_path.to_string_lossy(), enclave_debug, enclave_spid, enclave_peer_manager_tx, attestation_manager_tx).context("error creating enclave")?; + let node_id = enclave.start_replica(start_replica_request).context("error starting replica in enclave")?; + + node_id_tx.send(node_id)?; + + let mut enclave_manager = EnclaveManager::new(enclave_manager_channel, vec![enclave]); + while let Err(enclave_error) = enclave_manager.run() { + error!("fatal enclave error, retrying in 1 second: {}", enclave_error); + thread::sleep(Duration::from_secs(1)); + } + + drop(enclave_join_tx); + Ok(()) + }); + + let node_id = match node_id_rx.recv() { + Ok(node_id) => node_id, + Err(std::sync::mpsc::RecvError) => { + return Err(enclave_thread.join().unwrap().unwrap_err()); + } + }; + + let peer_listener = PeerListener::new(peer_listen_address, cmdline_config.kbuptlsd_bin_path.to_owned(), tls_max_connections, peer_tls_server_args, peer_manager_tx.clone()).context("error starting peer listener")?; + let peer_manager = PeerManager::new(peer_manager_tx.clone(), ENCLAVE_NAME.to_string(), enclave_manager_tx.clone(), node_id, peer_tls_client.clone()); + let attestation_manager = AttestationManager::new(enclave_manager_tx.clone(), maybe_intel_client.clone()); + let control_listener = ControlListener::new(config.control.listenHostPort, enclave_manager_tx.clone()).context("error starting control listener")?; + let timer_tick_task = EnclaveTimerTickTask::new(timer_tick_interval, ENCLAVE_NAME.to_string(), enclave_manager_tx.clone()); + let status_refresh_task = EnclaveStatusRefreshTask::new(ENCLAVE_STATUS_REFRESH_INTERVAL, enclave_manager_tx.clone()); + let sig_rl_refresh_task = maybe_intel_client.map(|intel_client: IntelClient| RevocationListRefreshTask::new(REVOCATION_LIST_REFRESH_INTERVAL, intel_client, enclave_manager_tx.clone())); + + runtime.spawn(peer_listener.into_future()); + runtime.spawn(peer_manager_rx.enter_loop(peer_manager)); + runtime.spawn(attestation_manager_rx.enter_loop(attestation_manager)); + runtime.spawn(control_listener.into_future()); + runtime.spawn(timer_tick_task.into_future()); + runtime.spawn(status_refresh_task.into_future()); + if let Some(sig_rl_refresh_task) = sig_rl_refresh_task { + runtime.spawn(sig_rl_refresh_task.into_future()); + } + + if let Some(metrics_config) = config.metrics { + for reporter_config in metrics_config.reporters { + let MetricsReporterConfig::Json(json_reporter_config) = reporter_config; + + let mut reporter_http_connector = HttpConnector::new(1); + reporter_http_connector.enforce_http(false); + + let reporter_tls_proxy = TlsClientProxySpawner::new(cmdline_config.kbuptlsd_bin_path.to_owned(), TlsClientProxyArguments::NoConfig { + ca: TlsClientProxyCaArgument::System, + key_file: None, + hostname: TlsClientProxyHostnameArgument::Hostname(json_reporter_config.hostname.to_string()), + }).context("error creating metrics json reporter tls proxy client")?; + + let reporter_tls_connector = TlsProxyConnector::new(Arc::new(reporter_tls_proxy), reporter_http_connector); + + let reporter_interval = json_reporter_config.intervalSeconds.map(Duration::from_secs) + .unwrap_or(DEFAULT_METRICS_INTERVAL); + let json_reporter = JsonReporter::new(&json_reporter_config.token, &json_reporter_config.hostname, cmdline_config.full_hostname, reporter_tls_connector).context("error creating metrics json reporter")?; + let periodic_reporter = PeriodicReporter::new(json_reporter, METRICS.clone(), reporter_interval); + + periodic_reporter.start(); + } + } + + unix_signal::ignore_signal(SIGPIPE).context("error setting sigaction")?; + unix_signal::ignore_signal(SIGCHLD).context("error setting sigaction")?; + + let unix_signals = unix_signal::handle_signals(vec![ + SIGHUP, + SIGINT, + SIGQUIT, + SIGTERM, + SIGUSR1, + SIGUSR2, + ]); + let unix_signals = runtime.block_on(unix_signals)?; + let enclave_manager_tx_2 = enclave_manager_tx.clone(); + let exit_signals_enabled = cmdline_config.exit_signals_enabled; + let handled_unix_signals = unix_signals.for_each(move |signum: signal::Signal| { + match signum { + SIGINT | + SIGQUIT | + SIGTERM + if exit_signals_enabled => + { + warn!("shutting down due to unix signal {}", signum); + let _ignore = enclave_manager_tx_2.cast(|enclave_manager: &mut EnclaveManager| { + enclave_manager.stop() + }); + } + _ => { + info!("received unix signal {}", signum); + } + } + Ok(()) + }); + let unix_signal_task = handled_unix_signals.map_err(|error: std::io::Error| { + error!("error in unix signal handler: {}", error); + }); + runtime.spawn(unix_signal_task); + + let source_partition = if let Some(source_partition_config) = config.enclave.sourcePartition { + let source_partition_range = PartitionKeyRangePb { + first: BackupId { id: source_partition_config.firstBackupId.to_vec() }, + last: BackupId { id: source_partition_config.lastBackupId.to_vec() }, + }; + + let source_replica_addresses = + source_partition_config.replicas.into_iter() + .map(|peer| peer.hostPort) + .collect(); + + let peer_tls_client = peer_tls_client.clone(); + let source_node_ids = peer_manager_tx.call(move |peer_manager: &mut PeerManager, reply_tx| { + peer_manager.discover_peers(source_replica_addresses, &peer_tls_client, reply_tx) + }); + + let peer_manager_tx = peer_manager_tx.clone(); + let source_partition = source_node_ids.map(move |source_node_ids: Vec| { + let source_node_ids_2 = source_node_ids.clone(); + let _ignore = peer_manager_tx.cast(move |peer_manager: &mut PeerManager| { + peer_manager.set_source_partition(source_node_ids_2) + }); + Some(SourcePartitionConfig { + node_ids: source_node_ids.into_iter().map(Vec::from).collect(), + range: source_partition_range, + }) + }); + let source_partition = source_partition.map_err(|error| { + error!("error discovering source replicas: {}", error); + }); + futures::future::Either::A(source_partition) + } else { + futures::future::Either::B(Ok(None).into_future()) + }; + + let peer_replica_addresses = + config.enclave.replicas.into_iter() + .map(|peer| peer.hostPort) + .collect(); + + let peer_node_ids = peer_manager_tx.call(move |peer_manager: &mut PeerManager, reply_tx| { + peer_manager.discover_peers(peer_replica_addresses, &peer_tls_client, reply_tx) + }); + let peer_node_ids = peer_node_ids.map_err(|error| { + error!("error discovering peer replicas: {}", error); + }); + + let replica_group_config = EnclaveReplicaGroupConfig { + storage_size: config.enclave.storageSize, + raft_log_data_size: config.enclave.raftLogSize, + raft_log_index_size: (config.enclave.raftLogSize / 128) as u32, // XXX correct ratio here + }; + + let discovered_peers = source_partition.join(peer_node_ids); + let replica_group_started = discovered_peers.map(move |(source_partition, peer_node_ids): (Option, Vec)| { + if peer_node_ids.iter().all(|peer_node_id| peer_node_id < &node_id) { + let _ignore = enclave_manager_tx.cast(move |enclave_manager: &mut EnclaveManager| { + enclave_manager.start_replica_group(ENCLAVE_NAME, StartReplicaGroupRequest { + peer_node_ids: peer_node_ids.into_iter().map(Vec::from).collect(), + config: replica_group_config, + source_partition, + }) + }); + } + }); + runtime.spawn(replica_group_started); + + let enclave_thread_joiner = Box::new(enclave_join_rx.then(move |_| enclave_thread.join())); + Ok(Self { runtime, enclave_thread_joiner }) + } + + pub fn join(mut self) { + match self.runtime.block_on(self.enclave_thread_joiner) { + Ok(Ok(())) => info!("enclave shutdown"), + Ok(Err(enclave_error)) => error!("enclave error: {}", enclave_error), + Err(_join_error) => error!("enclave thread died"), + } + drop(self.runtime); + } +} diff --git a/service/kbupd/src/tls.rs b/service/kbupd/src/tls.rs new file mode 100644 index 0000000..3c1ddc1 --- /dev/null +++ b/service/kbupd/src/tls.rs @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::io; +use std::net::{SocketAddr}; +use std::sync::*; +use std::os::unix::prelude::*; +use std::path::{PathBuf}; + +use futures::prelude::*; +use kbuptlsd::prelude::*; +use log::{warn, debug, log}; + +#[derive(Clone)] +pub struct TlsClient { + spawner: Arc, +} + +impl TlsClient { + pub fn new(bin_path: PathBuf, args: TlsClientProxyArguments) -> Result { + Ok(Self { + spawner: Arc::new(TlsClientProxySpawner::new(bin_path, args)?), + }) + } + pub fn spawn(&self, stream: impl AsRawFd, address: SocketAddr) -> Result { + let child = self.spawner.spawn(stream, address)?; + let child_pid = child.pid(); + + let (tls_stream, stderr_stream) = child.into_streams()?; + + let log_target = format!("kbuptlsd-{}", child_pid); + let stderr_logger = stderr_stream.for_each(move |line: String| { + let (log_level, line) = kbuptlsd::child::logger::parse_line(&line); + log!(target: &log_target, log_level, "{} => {}", address, line); + Ok(()) + }); + let stderr_logger = stderr_logger.then(move |result: Result<(), io::Error>| { + match result { + Ok(()) => { + debug!("{} => child process died", address); + Ok(()) + } + Err(error) => { + warn!("{} => error reading from child stderr: {}", address, error); + Err(()) + } + } + }); + tokio::spawn(stderr_logger); + + Ok(tls_stream) + } +} diff --git a/service/kbupd/src/unix_signal.rs b/service/kbupd/src/unix_signal.rs new file mode 100644 index 0000000..123b052 --- /dev/null +++ b/service/kbupd/src/unix_signal.rs @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use futures::prelude::*; +use futures::stream::{Fuse}; +use nix::sys::signal::{SaFlags, SigAction, SigHandler, SigSet, sigaction}; + +pub struct MergeSignals { + signals: Vec>, +} + +pub fn ignore_signal(signum: nix::sys::signal::Signal) -> nix::Result<()> { + let ignore_sigaction = SigAction::new(SigHandler::SigIgn, SaFlags::empty(), SigSet::empty()); + unsafe { sigaction(signum, &ignore_sigaction) }?; + Ok(()) +} + +pub fn handle_signals(signums: impl IntoIterator) -> impl Future { + let signals = signums.into_iter().map(move |signum: nix::sys::signal::Signal| { + let signal = tokio_signal::unix::Signal::with_handle(signum as i32, &Default::default()); + signal.map(|signal: tokio_signal::unix::Signal| { + signal.fuse() + }) + }); + let merged = futures::future::join_all(signals).map(move |signals: Vec>| { + MergeSignals { + signals, + } + }); + + merged +} + +impl Stream for MergeSignals { + type Item = nix::sys::signal::Signal; + type Error = std::io::Error; + + fn poll(&mut self) -> Poll, Self::Error> { + let mut not_ready = false; + for signal in &mut self.signals { + match signal.poll()? { + Async::Ready(Some(signum)) => { + match nix::sys::signal::Signal::from_c_int(signum) { + Ok(signum) => return Ok(Some(signum).into()), + Err(_) => (), + } + } + Async::Ready(None) => (), + Async::NotReady => not_ready = true, + } + } + if not_ready { + Ok(Async::NotReady) + } else { + Ok(None.into()) + } + } +} diff --git a/service/kbupd/src/util.rs b/service/kbupd/src/util.rs new file mode 100644 index 0000000..8ea6348 --- /dev/null +++ b/service/kbupd/src/util.rs @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::path::{Path, PathBuf}; + +pub use kbupd_util::*; + +pub fn join_if_relative(dir_path: &Path, file_path: &Path) -> PathBuf { + if file_path.is_relative() { + dir_path.join(file_path) + } else { + file_path.to_owned() + } +} diff --git a/service/kbupd_api/.cargo/config b/service/kbupd_api/.cargo/config new file mode 100644 index 0000000..fe9c8d6 --- /dev/null +++ b/service/kbupd_api/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/target" diff --git a/service/kbupd_api/Cargo.toml b/service/kbupd_api/Cargo.toml new file mode 100644 index 0000000..b66a19f --- /dev/null +++ b/service/kbupd_api/Cargo.toml @@ -0,0 +1,11 @@ +[package] +authors = ["Open Whisper Systems"] +name = "kbupd_api" +version = "0.1.0" +license = "AGPL-3.0-or-later" +edition = "2018" + +[dependencies] +kbupd_util = { path = "../kbupd_util" } +serde = "1.0" +serde_derive = "1.0" diff --git a/service/kbupd_api/src/entities.rs b/service/kbupd_api/src/entities.rs new file mode 100644 index 0000000..423e9b0 --- /dev/null +++ b/service/kbupd_api/src/entities.rs @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(non_snake_case)] + +use std::array::{TryFromSliceError}; +use std::convert::{TryFrom, TryInto}; +use std::ops::{Deref}; + +use kbupd_util::base64; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[serde(transparent)] +pub struct BackupId(#[serde(with = "base64::SerdeFixedLengthBase64")] [u8; 32]); + +#[derive(Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PingResponse { +} + +#[derive(Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct GetTokenResponse { + pub backupId: BackupId, + + #[serde(with = "base64::SerdeFixedLengthBase64")] + pub token: [u8; 32], + + pub tries: u16, +} + +#[derive(Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RemoteAttestationRequest { + #[serde(with = "base64::SerdeFixedLengthBase64")] + pub clientPublic: [u8; 32], +} + +#[derive(Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RemoteAttestationResponse { + #[serde(with = "base64::SerdeFixedLengthBase64")] + pub serverEphemeralPublic: [u8; 32], + + #[serde(with = "base64::SerdeFixedLengthBase64")] + pub serverStaticPublic: [u8; 32], + + #[serde(with = "base64")] + pub quote: Vec, + + #[serde(with = "base64::SerdeFixedLengthBase64")] + pub iv: [u8; 12], + + #[serde(with = "base64")] + pub ciphertext: Vec, + + #[serde(with = "base64::SerdeFixedLengthBase64")] + pub tag: [u8; 16], + + #[serde(with = "base64")] + pub signature: Vec, + + pub certificates: String, + + pub signatureBody: String, +} + +#[derive(Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct KeyBackupRequest { + #[serde(with = "base64")] + pub requestId: Vec, + + #[serde(with = "base64::SerdeFixedLengthBase64")] + pub iv: [u8; 12], + + #[serde(with = "base64")] + pub data: Vec, + + #[serde(with = "base64::SerdeFixedLengthBase64")] + pub mac: [u8; 16], +} + +#[derive(Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct KeyBackupResponse { + #[serde(with = "base64::SerdeFixedLengthBase64")] + pub iv: [u8; 12], + + #[serde(with = "base64")] + pub data: Vec, + + #[serde(with = "base64::SerdeFixedLengthBase64")] + pub mac: [u8; 16], +} + +// +// BackupId impls +// + +impl Deref for BackupId { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl TryFrom<&[u8]> for BackupId { + type Error = TryFromSliceError; + fn try_from(value: &[u8]) -> Result { + Ok(Self(value.try_into()?)) + } +} + +impl From for [u8; 32] { + fn from(value: BackupId) -> Self { + value.0 + } +} + +impl From<[u8; 32]> for BackupId { + fn from(value: [u8; 32]) -> Self { + BackupId(value) + } +} diff --git a/service/kbupd_api/src/lib.rs b/service/kbupd_api/src/lib.rs new file mode 100644 index 0000000..554e726 --- /dev/null +++ b/service/kbupd_api/src/lib.rs @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod entities; diff --git a/service/kbupd_api_client/.cargo/config b/service/kbupd_api_client/.cargo/config new file mode 100644 index 0000000..fe9c8d6 --- /dev/null +++ b/service/kbupd_api_client/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/target" diff --git a/service/kbupd_api_client/Cargo.toml b/service/kbupd_api_client/Cargo.toml new file mode 100644 index 0000000..98daa4d --- /dev/null +++ b/service/kbupd_api_client/Cargo.toml @@ -0,0 +1,31 @@ +[package] +authors = ["Open Whisper Systems"] +name = "kbupd_api_client" +version = "0.1.0" +license = "AGPL-3.0-or-later" +edition = "2018" + +[dependencies] +base64 = "0.10" +bytes = "0.4" +env_logger = "0.6" +clap = "2.33" +cookie = "0.12" +failure = "0.1" +failure_derive = "0.1" +futures = "0.1" +http = "0.1" +hyper = "0.12" +hyper-tls = "0.3" +kbupd_api = { path = "../kbupd_api" } +kbupd_client = { path = "../kbupd_client" } +log = { version = "0.4", features = ["std"] } +native-tls = "0.2" +prost = "0.5" +rand = "0.6" +ring = "0.14" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" +tokio = "0.1" +try_future = "0.1" diff --git a/service/kbupd_api_client/src/lib.rs b/service/kbupd_api_client/src/lib.rs new file mode 100644 index 0000000..f941411 --- /dev/null +++ b/service/kbupd_api_client/src/lib.rs @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use cookie::{Cookie}; +use failure::{Fail, format_err, ResultExt}; +use futures::prelude::*; +use http::{Uri}; +use http::header; +use http::header::{HeaderValue}; +use http::response; +use hyper::{Body, Chunk, Method, Response, Request}; +use hyper::client::{HttpConnector}; +use hyper_tls::{HttpsConnector}; +use kbupd_api::entities::*; +use log::{debug}; +use native_tls::{Protocol, TlsConnector}; +use serde::{Deserialize, Serialize}; +use try_future::{try_future, TryFuture}; + +#[derive(Clone)] +pub struct KeyBackupApiClient { + client: hyper::Client, Body>, + base_uri: Uri, +} + +#[derive(Clone)] +pub struct KeyBackupApiCredentials { + pub username: String, + pub password: String, +} + +impl KeyBackupApiClient { + pub fn new(base_uri: Uri, insecure_ssl: bool) -> Result { + let tls_connector = TlsConnector::builder().min_protocol_version(Some(Protocol::Tlsv12)) + .danger_accept_invalid_certs(insecure_ssl) + .build() + .context("error creating tls connector")?; + + let mut http_connector = HttpConnector::new(1); + http_connector.enforce_http(false); + + let client = hyper::Client::builder().build((http_connector, tls_connector).into()); + Ok(Self { + client, + base_uri, + }) + } + + pub fn get_token( + &self, + credentials: &KeyBackupApiCredentials, + enclave_name: &str, + ) + -> impl Future + Send + 'static + { + let mut uri_parts = self.base_uri.clone().into_parts(); + let uri_path_and_query = try_future!(format!("/v1/token/{}", enclave_name).parse::().context("error creating request path")); + uri_parts.path_and_query = Some(uri_path_and_query); + let uri = try_future!(Uri::from_parts(uri_parts).context("error creating request uri")); + let response_with_parts = self.get_request(uri, credentials); + let response = response_with_parts.map(|(_parts, response)| response); + + response.into() + } + + pub fn backup_request( + &self, + credentials: &KeyBackupApiCredentials, + enclave_name: &str, + request: kbupd_client::Request, + ) + -> impl Future + Send + 'static + { + + let client = kbupd_client::Client::new(&mut rand::thread_rng()); + + let attestation_request = RemoteAttestationRequest { + clientPublic: *client.client_pubkey(), + }; + let attestation_response = self.get_attestation(credentials, enclave_name, attestation_request) + .map_err(|error| error.context("error during attestation request").into()); + + let state = self.clone(); + let credentials = credentials.clone(); + let enclave_name = enclave_name.to_string(); + + let response = attestation_response.and_then(move |(attestation_response_cookies, attestation_response): (Vec, RemoteAttestationResponse)| { + debug!("got attestation: {:?}", &attestation_response); + let negotiation = kbupd_client::RequestNegotiation { + server_ephemeral_pubkey: attestation_response.serverEphemeralPublic, + server_static_pubkey: attestation_response.serverStaticPublic, + encrypted_pending_request_id: kbupd_client::EncryptedMessage { + iv: attestation_response.iv, + mac: attestation_response.tag, + data: attestation_response.ciphertext, + }, + }; + let (encrypted_request, pending_request) = + try_future!(client.request(&mut rand::thread_rng(), negotiation, request)); + + let key_backup_request = KeyBackupRequest { + requestId: encrypted_request.pending_request_id, + iv: encrypted_request.encrypted_message.iv, + mac: encrypted_request.encrypted_message.mac, + data: encrypted_request.encrypted_message.data, + }; + let key_backup_response = state.put_backup_request(&credentials, &enclave_name, attestation_response_cookies, key_backup_request) + .map_err(|error| error.context("error during key backup request").into()); + + let response = key_backup_response.and_then(move |key_backup_response: KeyBackupResponse| { + let encrypted_response = kbupd_client::EncryptedMessage { + iv: key_backup_response.iv, + mac: key_backup_response.mac, + data: key_backup_response.data, + }; + pending_request.decrypt_reply(encrypted_response) + }); + response.into() + }); + + response + } + + pub fn get_attestation( + &self, + credentials: &KeyBackupApiCredentials, + enclave_name: &str, + request: RemoteAttestationRequest, + ) + -> impl Future>, RemoteAttestationResponse), Error = failure::Error> + Send + 'static + { + let mut uri_parts = self.base_uri.clone().into_parts(); + let uri_path_and_query = try_future!(format!("/v1/attestation/{}", enclave_name).parse::().context("error creating request path")); + uri_parts.path_and_query = Some(uri_path_and_query); + let uri = try_future!(Uri::from_parts(uri_parts).context("error creating request uri")); + let cookies = Vec::new(); + + let response_with_parts = self.put_request(uri, credentials, cookies, request); + let response_with_cookies = response_with_parts.and_then(|(response_parts, response): (response::Parts, RemoteAttestationResponse)| { + let cookie_headers = response_parts.headers.get_all(header::SET_COOKIE); + let cookies = cookie_headers.into_iter().map(|cookie_header: &HeaderValue| -> Result, failure::Error> { + let cookie_str = cookie_header.to_str()?; + let cookie = Cookie::parse(cookie_str)?; + Ok(cookie.into_owned()) + }); + let cookies_vec = cookies.collect::, _>>()?; + Ok((cookies_vec, response)) + }); + + response_with_cookies.into() + } + + pub fn put_backup_request( + &self, + credentials: &KeyBackupApiCredentials, + enclave_name: &str, + cookies: Vec>, + request: KeyBackupRequest, + ) + -> impl Future + Send + 'static + { + let mut uri_parts = self.base_uri.clone().into_parts(); + let uri_path_and_query = try_future!(format!("/v1/backup/{}", enclave_name).parse::().context("error creating request path")); + uri_parts.path_and_query = Some(uri_path_and_query); + let uri = try_future!(Uri::from_parts(uri_parts).context("error creating request uri")); + let response_with_parts = self.put_request(uri, credentials, cookies, request); + let response = response_with_parts.map(|(_parts, response)| response); + response.into() + } + + fn get_request( + &self, + uri: Uri, + credentials: &KeyBackupApiCredentials, + ) + -> impl Future + Send + 'static + where ResponseTy: for<'de> Deserialize<'de> + Send + 'static, + { + let mut hyper_request = Request::new(Body::empty()); + + *hyper_request.uri_mut() = uri; + hyper_request.headers_mut().insert("Authorization", credentials.into()); + + let response = self.client.request(hyper_request) + .map_err(failure::Error::from); + let decoded_response = response.and_then(Self::decode_response); + decoded_response + } + + fn put_request( + &self, + uri: Uri, + credentials: &KeyBackupApiCredentials, + cookies: Vec>, + request: RequestTy, + ) + -> impl Future + Send + 'static + where RequestTy: Serialize + 'static, + ResponseTy: for<'de> Deserialize<'de> + Send + 'static, + { + let encoded_request = try_future!(serde_json::to_vec(&request).context("error serializing request as json")); + let mut hyper_request = Request::new(Body::from(encoded_request)); + + *hyper_request.method_mut() = Method::PUT; + *hyper_request.uri_mut() = uri; + hyper_request.headers_mut().insert(header::CONTENT_TYPE, HeaderValue::from_static("application/json")); + hyper_request.headers_mut().insert(header::AUTHORIZATION, credentials.into()); + + for cookie in cookies { + let cookie_header_value = try_future!(HeaderValue::from_str(&format!("{}={}", cookie.name(), cookie.value())).context("invalid cookie from server attestation response")); + hyper_request.headers_mut().insert(header::COOKIE, cookie_header_value); + } + + let response = self.client.request(hyper_request) + .map_err(failure::Error::from); + let decoded_response = response.and_then(Self::decode_response); + decoded_response.into() + } + + fn decode_response( + response: Response + ) + -> impl Future + Send + 'static + where ResponseTy: for<'de> Deserialize<'de> + Send + 'static, + { + let (response_parts, response_body) = response.into_parts(); + + if !response_parts.status.is_success() { + return TryFuture::from_error(format_err!("non-successful response code: {}", &response_parts.status)); + } + let response_data = response_body.concat2() + .map_err(failure::Error::from); + let decoded_response = response_data.and_then(|full_response: Chunk| { + match serde_json::from_slice(&full_response) { + Ok(decoded_response) => Ok((response_parts, decoded_response)), + Err(error) => { + debug!("invalid server response: {}\n{}", &error, String::from_utf8_lossy(&full_response.to_vec())); + Err(error.context("invalid server response").into()) + } + } + }); + decoded_response.into() + } +} + +// +// KeyBackupApiCredentials impls +// + +impl From<&KeyBackupApiCredentials> for HeaderValue { + fn from(from: &KeyBackupApiCredentials) -> Self { + let joined_credentials = format!("{}:{}", from.username, from.password); + + let mut authorization_header = "Basic ".to_string(); + base64::encode_config_buf(&joined_credentials, base64::STANDARD, &mut authorization_header); + HeaderValue::from_str(&authorization_header) + .unwrap_or_else(|error| panic!("invalid authorization header: {}", error)) + } +} diff --git a/service/kbupd_api_client/src/main.rs b/service/kbupd_api_client/src/main.rs new file mode 100644 index 0000000..da621a0 --- /dev/null +++ b/service/kbupd_api_client/src/main.rs @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fmt::{Display, Write as _}; +use std::io::{Cursor, Write as _}; +use std::str::{FromStr}; +use std::sync::atomic; +use std::sync::atomic::{AtomicU64}; +use std::sync::{Arc}; +use std::time::{SystemTime}; + +use bytes::{BufMut}; +use failure::{format_err, ResultExt}; +use futures::future; +use futures::prelude::*; +use http::{Uri}; +use log::{debug}; +use kbupd_api::entities::*; +use kbupd_api_client::*; +use rand::{RngCore}; + +fn main() -> Result<(), failure::Error> { + let arguments = parse_arguments(); + + let (subcommand_name, subcommand_arguments) = arguments.subcommand(); + let subcommand_name = leak_argument(subcommand_name); + let subcommand_arguments = subcommand_arguments.unwrap(); + + let connect_uri = arguments.value_of("connect_uri").unwrap_or_default().parse::().context("invalid --connect uri")?; + let insecure_ssl = arguments.is_present("insecure"); + let debug = arguments.is_present("debug"); + let username = arguments.value_of("username").map(str::to_string); + let token_secret = parse_argument(arguments.value_of("token_secret"), parse_hex).context("invalid --token-secret")?.unwrap_or_default(); + let password = arguments.value_of("password").map(str::to_string); + + let log_level = if debug { + log::LevelFilter::Debug + } else { + log::LevelFilter::Info + }; + let mut logger = env_logger::Builder::from_default_env(); + logger.filter_level(log_level); + logger.default_format_timestamp_nanos(true); + logger.init(); + + let client = KeyBackupApiClient::new(connect_uri, insecure_ssl).context("error creating api client")?; + + let mut runtime = tokio::runtime::Runtime::new().context("error creating tokio runtime")?; + + match subcommand_name { + "delete" => { + let enclave_name = subcommand_arguments.value_of("enclave_name").unwrap_or_default().to_string(); + let service_id = subcommand_arguments.value_of("service_id").map(parse_hex).transpose().context("invalid --service-id")?; + let request_count = u64::from_str(arguments.value_of("request_count").unwrap_or_default()).context("invalid --request-count")?; + let max_parallel = u64::from_str(arguments.value_of("max_parallel").unwrap_or("1")).context("invalid --max-parallel")?; + let backup_id = subcommand_arguments.value_of("backup_id").map(parse_hex_bytes::<[u8; 32]>).transpose().context("invalid --backup-id")?; + let username = username.unwrap_or_else(rand_username); + let credentials = calculate_credentials(username, password, &token_secret); + + let parallel_count = max_parallel.min(request_count); + let requested = Arc::new(AtomicU64::new(0)); + + let backup_id = match backup_id { + Some(backup_id) => { + future::Either::A(Ok(backup_id).into_future()) + } + None => { + let token_response = client.get_token(&credentials, &enclave_name) + .map_err(|error| error.context("error during token request")); + let backup_id = token_response.map(|token_response: GetTokenResponse| { + token_response.backupId.into() + }); + future::Either::B(backup_id) + } + }; + let backup_id = runtime.block_on(backup_id)?; + let request_loop = move |()| { + if requested.fetch_add(1, atomic::Ordering::SeqCst) + 1 > request_count { + return future::Either::A(Ok(future::Loop::Break(())).into_future()); + } + let delete_request = kbupd_client::DeleteRequest { + service_id: service_id.clone(), + backup_id: Some(backup_id.to_vec()), + }; + debug!("sending request: {}", &delete_request); + let request = kbupd_client::Request { + backup: None, + restore: None, + delete: Some(delete_request), + }; + + let response = client.backup_request(&credentials, &enclave_name, request) + .map_err(|error| error.context("error during delete request").into()); + + let continued = response.and_then(move |response: kbupd_client::Response| { + if let Some(delete_response) = response.delete { + debug!("server response: {}", &delete_response); + Ok(future::Loop::Continue(())) + } else { + Err(format_err!("server response was empty: {:?}", &response)) + } + }); + future::Either::B(continued) + }; + let futures = (0..parallel_count).map(move |_| future::loop_fn((), request_loop.clone())); + let responses = futures::stream::futures_unordered(futures).for_each(|_| Ok(())); + + runtime.block_on(responses)?; + } + "backup" | "restore" => { + let enclave_name = leak_argument(subcommand_arguments.value_of("enclave_name").unwrap_or_default()); + let service_id = leak_argument(&subcommand_arguments.value_of("service_id").map(parse_hex).transpose().context("invalid --service-id")?); + let request_count = u64::from_str(arguments.value_of("request_count").unwrap_or_default()).context("invalid --request-count")?; + let max_parallel = u64::from_str(arguments.value_of("max_parallel").unwrap_or("1")).context("invalid --max-parallel")?; + let backup_data = parse_argument(subcommand_arguments.value_of("backup_data"), parse_hex_bytes::<[u8; 32]>).context("invalid --backup-data")?; + let backup_pin = parse_argument(subcommand_arguments.value_of("backup_pin"), parse_hex_bytes::<[u8; 32]>).context("invalid --backup-pin")?; + let backup_tries = parse_argument(subcommand_arguments.value_of("backup_tries"), u32::from_str).context("invalid --backup-tries")?; + let valid_from = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) + .expect("system clock is not set") + .as_secs() + .saturating_sub(86400); + + let parallel_count = max_parallel.min(request_count); + let requested = Arc::new(AtomicU64::new(0)); + + struct RequestState { + credentials: KeyBackupApiCredentials, + backup_id: Vec, + token: Vec, + } + #[derive(Default)] + struct RequestLoopState { + request_state: Option, + responses: Vec>, + } + let request_loop = move |state: RequestLoopState| { + let RequestLoopState { request_state, mut responses } = state; + + if requested.fetch_add(1, atomic::Ordering::SeqCst) + 1 > request_count { + return future::Either::A(Ok(future::Loop::Break(responses)).into_future()); + } + + let request_state = if let Some(request_state) = request_state { + future::Either::A(Ok(request_state).into_future()) + } else { + let credentials = calculate_credentials(username.clone().unwrap_or_else(rand_username), password.clone(), &token_secret); + + debug!("fetching token for {}", &credentials.username); + + let token_response = client.get_token(&credentials, enclave_name) + .map_err(|error| error.context("error during token request").into()); + let request_state = token_response.map(move |token_response: GetTokenResponse| { + RequestState { + credentials, + backup_id: token_response.backupId.to_vec(), + token: token_response.token.to_vec(), + } + }); + + future::Either::B(request_state) + }; + let client = client.clone(); + let looped = request_state.and_then(move |request_state: RequestState| { + let RequestState { credentials, backup_id, token } = request_state; + let request = match subcommand_name { + "backup" => { + let backup_request = kbupd_client::BackupRequest { + service_id: service_id.clone(), + backup_id: Some(backup_id.clone()), + token: Some(token), + valid_from: Some(valid_from), + data: Some(backup_data.clone().unwrap_or_else(rand_bytes).to_vec()), + pin: Some(backup_pin.clone().unwrap_or_else(rand_bytes).to_vec()), + tries: Some(backup_tries.unwrap_or(10)), + }; + debug!("sending request: {}", &backup_request); + kbupd_client::Request { + backup: Some(backup_request), + restore: None, + delete: None, + } + }, + "restore" => { + let restore_request = kbupd_client::RestoreRequest { + service_id: service_id.clone(), + backup_id: Some(backup_id.clone()), + token: Some(token), + valid_from: Some(valid_from), + pin: Some(backup_pin.clone().unwrap_or_else(rand_bytes).to_vec()), + }; + debug!("sending request: {}", &restore_request); + kbupd_client::Request { + backup: None, + restore: Some(restore_request), + delete: None, + } + } + _ => unreachable!(), + }; + let response = client.backup_request(&credentials, enclave_name, request) + .map_err(|error| error.context("error during key backup request").into()); + let looped = response.and_then(move |response: kbupd_client::Response| { + match subcommand_name { + "backup" => { + let backup_response = match response.backup { + Some(backup_response) => backup_response, + None => return Err(format_err!("server response was empty: {:?}", &response)), + }; + debug!("server response: {}", &backup_response); + let token = match backup_response.token.clone() { + Some(token) => token, + None => return Err(format_err!("server backup response did not contain a token")), + }; + responses.push(Box::new(backup_response)); + let state = RequestLoopState { + request_state: Some(RequestState { credentials, backup_id, token }), + responses + }; + Ok(future::Loop::Continue(state)) + } + "restore" => { + let restore_response = match response.restore { + Some(restore_response) => restore_response, + None => return Err(format_err!("server response was empty: {:?}", &response)), + }; + debug!("server response: {}", &restore_response); + responses.push(Box::new(restore_response)); + let state = RequestLoopState { + request_state: None, + responses + }; + Ok(future::Loop::Continue(state)) + } + _ => unreachable!(), + } + }); + looped + }); + future::Either::B(looped) + }; + let futures = (0..parallel_count).map(move |_| future::loop_fn(Default::default(), request_loop.clone())); + let responses = futures::stream::futures_unordered(futures).collect(); + let responses = runtime.block_on(responses)?; + + let stdout = std::io::stdout(); + let mut stdout_lock = stdout.lock(); + for response in responses.iter().flatten() { + write!(stdout_lock, "{}\n", response)?; + } + } + _ => unreachable!(), + } + + Ok(()) +} + +fn parse_argument(maybe_argument: Option, parse_fun: F) -> Result, E> +where F: Fn(T) -> Result +{ + if let Some(argument) = maybe_argument { + Ok(Some(parse_fun(argument)?)) + } else { + Ok(None) + } +} + +fn parse_hex_bytes(hex: &str) -> Result where T: AsRef<[u8]> + AsMut<[u8]> + Default { + let mut buf = Cursor::new(T::default()); + if hex.len() == buf.remaining_mut().saturating_mul(2) { + parse_hex_buf(hex, &mut buf)?; + Ok(buf.into_inner()) + } else { + Err(format_err!("wrong size hexadecimal parameter {} != {}", hex.len(), buf.remaining_mut().saturating_mul(2))) + } +} + +fn parse_hex(hex: &str) -> Result, failure::Error> { + let mut bytes: Vec = Vec::with_capacity(hex.len() / 2); + parse_hex_buf(hex, &mut bytes)?; + Ok(bytes) +} + +fn parse_hex_buf(mut hex: &str, bytes: &mut impl BufMut) -> Result<(), failure::Error> { + while let Some(hex_byte_str) = hex.get(..2) { + let hex_byte = u8::from_str_radix(hex_byte_str, 16) + .with_context(|_| format_err!("invalid hexadecimal byte '{}': {}", hex_byte_str, hex))?; + bytes.put(hex_byte); + hex = hex.get(2..).unwrap_or_default(); + } + if hex.is_empty() { + Ok(()) + } else { + Err(format_err!("truncated hexademical: {}", hex)) + } +} + +fn rand_bytes() -> T where T: AsMut<[u8]> + Default { + let mut buf = T::default(); + rand::thread_rng().fill_bytes(buf.as_mut()); + buf +} + +fn calculate_credentials(username: String, password: Option, token_secret: &[u8]) -> KeyBackupApiCredentials { + let password = password.unwrap_or_else(|| calculate_password(&username, token_secret)); + KeyBackupApiCredentials { + username, + password, + } +} + +fn rand_username() -> String { + let mut rand = rand::thread_rng(); + format!("test_{:016x}{:016x}", rand.next_u64(), rand.next_u64()) +} + +fn calculate_password(username: &str, token_secret: &[u8]) -> String { + let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) + .expect("system clock is not set") + .as_secs(); + let sign_data = format!("{}:{}", username, timestamp); + let sign_key = ring::hmac::SigningKey::new(&ring::digest::SHA256, token_secret); + let signature = ring::hmac::sign(&sign_key, sign_data.as_bytes()); + let mut token = sign_data; + token.push(':'); + for byte in &signature.as_ref()[..10] { + write!(&mut token, "{:02x}", byte).unwrap_or_else(|_| unreachable!()); + } + token +} + +fn leak_argument(argument: &T) -> &'static T +where T: ToOwned + ?Sized, + U: std::borrow::Borrow, + Box: From, +{ + Box::leak(argument.to_owned().into()) +} + +fn parse_arguments() -> clap::ArgMatches<'static> { + let enclave_name_argument = + clap::Arg::with_name("enclave_name") + .takes_value(true) + .required(true) + .long("enclave-name") + .value_name("enclave_name") + .help("Name of enclave to query, in a frontend kbupd instance"); + + let service_id_argument = + clap::Arg::with_name("service_id") + .takes_value(true) + .long("service-id") + .value_name("service_id_hex") + .help("Service ID to use in client request, as a hexadecimal byte string"); + + let backup_id_argument = + clap::Arg::with_name("backup_id") + .takes_value(true) + .long("backup-id") + .value_name("backup_id_hex") + .help("Backup ID to use in client request, as a hexadecimal byte string."); + + let delete_subcommand = + clap::SubCommand::with_name("delete") + .arg(enclave_name_argument.clone()) + .arg(service_id_argument.clone()) + .arg(backup_id_argument.clone()) + .about("Key Backup Service HTTP API Client - Delete"); + + let service_id_argument = + clap::Arg::with_name("service_id") + .takes_value(true) + .long("service-id") + .value_name("service_id_hex") + .help("Service ID to use in client request, as a hexadecimal byte string"); + + let backup_data_argument = + clap::Arg::with_name("backup_data") + .takes_value(true) + .long("backup-data") + .value_name("backup_data_hex") + .help("Backup Key to use in client request, as a hexadecimal byte string. Randomly generated by default."); + + let backup_pin_argument = + clap::Arg::with_name("backup_pin") + .takes_value(true) + .long("backup-pin") + .value_name("backup_pin_hex") + .help("Backup PIN to use in client request, as a hexadecimal byte string. Randomly generated by default."); + + let backup_tries_argument = + clap::Arg::with_name("backup_tries") + .takes_value(true) + .default_value("10") + .long("backup-tries") + .value_name("backup_tries") + .help("Backup try count to use in client request, in decimal"); + + let backup_subcommand = + clap::SubCommand::with_name("backup") + .arg(enclave_name_argument.clone()) + .arg(service_id_argument.clone()) + .arg(backup_data_argument) + .arg(backup_pin_argument.clone()) + .arg(backup_tries_argument) + .about("Key Backup Service HTTP API Client - Backup"); + + let restore_subcommand = + clap::SubCommand::with_name("restore") + .arg(enclave_name_argument) + .arg(service_id_argument) + .arg(backup_pin_argument) + .about("Key Backup Service HTTP API Client - Restore"); + + let connect_argument = + clap::Arg::with_name("connect_uri") + .takes_value(true) + .required(true) + .long("connect") + .value_name("connect_uri") + .help("Base URI of HTTPS API to connect to (e.g. https://api.backup.signal.org/)"); + + let insecure_argument = + clap::Arg::with_name("insecure") + .long("insecure") + .help("Allow insecure server connections when using SSL"); + + let username_argument = + clap::Arg::with_name("username") + .takes_value(true) + .required_unless("token_secret") + .long("username") + .value_name("username") + .help("Username to authenticate with, as assigned by a Signal server. Randomly generated by default, if --token-secret is provided."); + + let password_argument = + clap::Arg::with_name("password") + .takes_value(true) + .long("password") + .value_name("password") + .help("Password to authenticate with, as assigned by a Signal server"); + + let token_secret_argument = + clap::Arg::with_name("token_secret") + .takes_value(true) + .long("token-secret") + .value_name("hex_secret") + .help("Secret used to generate auth tokens, as a hexadecimal byte string"); + + let authentication_secret_group = + clap::ArgGroup::with_name("auth_secret") + .required(true) + .arg("password") + .arg("token_secret"); + + let request_count_argument = + clap::Arg::with_name("request_count") + .takes_value(true) + .default_value("1") + .long("request-count") + .value_name("request_count") + .help("Number of requests to perform, in decimal. For request parameters randomized by default, different values will be generated for each request."); + + let max_parallel_argument = + clap::Arg::with_name("max_parallel") + .takes_value(true) + .conflicts_with("username") + .long("max-parallel") + .value_name("max_parallel") + .help("Maximum number of requests to perform in parallel, in decimal") + .long_help("Parallel requests are only possible if no --username is provided."); + + let debug_argument = + clap::Arg::with_name("debug") + .long("debug") + .help("emit debug logging"); + + clap::App::new("kbupd_api_client") + .version(clap::crate_version!()) + .about(format!("{} -- HTTP API Client", clap::crate_description!()).as_str()) + .author(clap::crate_authors!()) + .setting(clap::AppSettings::VersionlessSubcommands) + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .arg(connect_argument) + .arg(insecure_argument) + .arg(username_argument) + .arg(password_argument) + .arg(token_secret_argument) + .group(authentication_secret_group) + .arg(request_count_argument) + .arg(max_parallel_argument) + .arg(debug_argument) + .subcommand(delete_subcommand) + .subcommand(backup_subcommand) + .subcommand(restore_subcommand) + .get_matches() +} diff --git a/service/kbupd_client/.cargo/config b/service/kbupd_client/.cargo/config new file mode 100644 index 0000000..fe9c8d6 --- /dev/null +++ b/service/kbupd_client/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/target" diff --git a/service/kbupd_client/Cargo.toml b/service/kbupd_client/Cargo.toml new file mode 100644 index 0000000..cd71261 --- /dev/null +++ b/service/kbupd_client/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors = ["Open Whisper Systems"] +name = "kbupd_client" +version = "0.1.0" +license = "AGPL-3.0-or-later" +edition = "2018" + +[dependencies] +bytes = "0.4" +failure = "0.1" +prost = "0.5" +rand = "0.6" +ring = "0.14" +x25519-dalek = { version = "0.5", default-features = false, features = ["u64_backend"] } + +[build-dependencies] +prost-build = "0.5" diff --git a/service/kbupd_client/build.rs b/service/kbupd_client/build.rs new file mode 100644 index 0000000..336edf2 --- /dev/null +++ b/service/kbupd_client/build.rs @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +fn main() { + prost_build::compile_protos(&["src/kbupd_client.proto"], &["src/"]) + .expect("error compiling protobufs"); +} diff --git a/service/kbupd_client/src/kbupd_client.proto b/service/kbupd_client/src/kbupd_client.proto new file mode 100644 index 0000000..a39f7a3 --- /dev/null +++ b/service/kbupd_client/src/kbupd_client.proto @@ -0,0 +1,79 @@ +syntax = "proto2"; + +package protobufs.kbupd_client; + +message Request { + optional BackupRequest backup = 1; + optional RestoreRequest restore = 2; + optional DeleteRequest delete = 3; +} + +message Response { + optional BackupResponse backup = 1; + optional RestoreResponse restore = 2; + optional DeleteResponse delete = 3; +} + +// +// backup +// + +message BackupRequest { + optional bytes service_id = 1; + optional bytes backup_id = 2; + optional bytes token = 3; + optional uint64 valid_from = 4; + optional bytes data = 5; + optional bytes pin = 6; + optional uint32 tries = 7; +} + +message BackupResponse { + enum Status { + OK = 1; + ALREADY_EXISTS = 2; + NOT_YET_VALID = 3; + } + + optional Status status = 1; + optional bytes token = 2; +} + +// +// restore +// + +message RestoreRequest { + optional bytes service_id = 1; + optional bytes backup_id = 2; + optional bytes token = 3; + optional uint64 valid_from = 4; + optional bytes pin = 5; +} + +message RestoreResponse { + enum Status { + OK = 1; + TOKEN_MISMATCH = 2; + NOT_YET_VALID = 3; + MISSING = 4; + PIN_MISMATCH = 5; + } + + optional Status status = 1; + optional bytes token = 2; + optional bytes data = 3; + optional uint32 tries = 4; +} + +// +// delete +// + +message DeleteRequest { + optional bytes service_id = 1; + optional bytes backup_id = 2; +} + +message DeleteResponse { +} diff --git a/service/kbupd_client/src/lib.rs b/service/kbupd_client/src/lib.rs new file mode 100644 index 0000000..6fe12d2 --- /dev/null +++ b/service/kbupd_client/src/lib.rs @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +mod protobufs; +mod protobufs_impl; + +use failure::{ResultExt}; +use prost::{Message}; +use rand::{Rng}; + +pub use crate::protobufs::kbupd_client::*; + +pub struct Client { + client_privkey: x25519_dalek::StaticSecret, + client_pubkey: x25519_dalek::PublicKey, +} + +#[derive(Default)] +pub struct RequestNegotiation { + pub server_ephemeral_pubkey: [u8; 32], + pub server_static_pubkey: [u8; 32], + pub encrypted_pending_request_id: EncryptedMessage, +} + +pub struct EncryptedRequest { + pub pending_request_id: Vec, + pub encrypted_message: EncryptedMessage, +} + +#[derive(Default)] +pub struct EncryptedMessage { + pub iv: [u8; 12], + pub mac: [u8; 16], + pub data: Vec, +} + +impl Client { + pub fn new(random: &mut (impl rand::RngCore + rand::CryptoRng)) -> Self { + let client_privkey = x25519_dalek::StaticSecret::new(random); + let client_pubkey = x25519_dalek::PublicKey::from(&client_privkey); + Self { + client_privkey, + client_pubkey, + } + } + + pub fn client_pubkey(&self) -> &[u8; 32] { + self.client_pubkey.as_bytes() + } + + pub fn request(self, + random: &mut (impl rand::RngCore + rand::CryptoRng), + negotiation: RequestNegotiation, + request: Request) + -> Result<(EncryptedRequest, PendingRequest), failure::Error> + { + let (client_key, server_key) = key_agreement(&self.client_privkey, + &self.client_pubkey, + &negotiation.server_ephemeral_pubkey, + &negotiation.server_static_pubkey); + + let ring_server_key = ring::aead::OpeningKey::new(&ring::aead::AES_256_GCM, &server_key) + .context("invalid server_key")?; + let ring_client_key = ring::aead::SealingKey::new(&ring::aead::AES_256_GCM, &client_key) + .context("invalid client_key")?; + + let pending_request_id_len = negotiation.encrypted_pending_request_id.data.len(); + let mut pending_request_id = negotiation.encrypted_pending_request_id.data; + pending_request_id.extend_from_slice(&negotiation.encrypted_pending_request_id.mac); + let nonce = ring::aead::Nonce::assume_unique_for_key(negotiation.encrypted_pending_request_id.iv); + ring::aead::open_in_place(&ring_server_key, + nonce, + ring::aead::Aad::empty(), 0, + &mut pending_request_id) + .context("decrypt error")?; + pending_request_id.truncate(pending_request_id_len); + + let request_data_len = request.encoded_len(); + let mut encrypted_message = EncryptedMessage { + iv: [0; 12], + mac: [0; 16], + data: Vec::with_capacity(request_data_len + 16), + }; + request.encode(&mut encrypted_message.data)?; + encrypted_message.data.extend_from_slice(&encrypted_message.mac); + + random.fill(&mut encrypted_message.iv); + let nonce = ring::aead::Nonce::assume_unique_for_key(encrypted_message.iv); + ring::aead::seal_in_place(&ring_client_key, + nonce, + ring::aead::Aad::from(&pending_request_id), + &mut encrypted_message.data, + encrypted_message.mac.len()) + .context("encrypt error")?; + encrypted_message.mac.copy_from_slice(&encrypted_message.data[request_data_len..]); + encrypted_message.data.truncate(request_data_len); + + let encrypted_request = EncryptedRequest { + pending_request_id, + encrypted_message, + }; + let pending_request = PendingRequest { + server_key, + }; + + Ok((encrypted_request, pending_request)) + } +} + +pub struct PendingRequest { + server_key: [u8; 32], +} + +impl PendingRequest { + pub fn decrypt_reply(self, mut reply: EncryptedMessage) -> Result { + let reply_data_len = reply.data.len(); + reply.data.extend_from_slice(&reply.mac); + let ring_server_key = ring::aead::OpeningKey::new(&ring::aead::AES_256_GCM, &self.server_key) + .context("invalid server_key")?; + let nonce = ring::aead::Nonce::assume_unique_for_key(reply.iv); + ring::aead::open_in_place(&ring_server_key, + nonce, + ring::aead::Aad::empty(), 0, + &mut reply.data) + .context("decrypt error")?; + let reply_data = &reply.data[..reply_data_len]; + + Ok(Response::decode(reply_data)?) + } +} + +fn key_agreement(client_privkey: &x25519_dalek::StaticSecret, + client_pubkey: &x25519_dalek::PublicKey, + server_ephemeral_pubkey: &[u8; 32], + server_static_pubkey: &[u8; 32]) + -> ([u8; 32], [u8; 32]) +{ + let server_ephemeral_pubkey = x25519_dalek::PublicKey::from(*server_ephemeral_pubkey); + let server_static_pubkey = x25519_dalek::PublicKey::from(*server_static_pubkey); + let hkdf_secret = { + let mut hkdf_secret: [u8; 64] = [0; 64]; + let ephemeral_dh_key = client_privkey.diffie_hellman(&server_ephemeral_pubkey); + let static_dh_key = client_privkey.diffie_hellman(&server_static_pubkey); + hkdf_secret[ 0..32].copy_from_slice(ephemeral_dh_key.as_bytes()); + hkdf_secret[32..64].copy_from_slice(static_dh_key.as_bytes()); + hkdf_secret + }; + let hkdf_salt = { + let mut hkdf_salt_bytes: [u8; 96] = [0; 96]; + hkdf_salt_bytes[ 0..32].copy_from_slice(client_pubkey.as_bytes()); + hkdf_salt_bytes[32..64].copy_from_slice(server_ephemeral_pubkey.as_bytes()); + hkdf_salt_bytes[64..96].copy_from_slice(server_static_pubkey.as_bytes()); + ring::hmac::SigningKey::new(&ring::digest::SHA256, &hkdf_salt_bytes) + }; + + let mut keys: [u8; 64] = [0; 64]; + let mut client_key: [u8; 32] = [0; 32]; + let mut server_key: [u8; 32] = [0; 32]; + + ring::hkdf::extract_and_expand(&hkdf_salt, &hkdf_secret, &[0; 0], &mut keys); + client_key.copy_from_slice(&keys[ 0..32]); + server_key.copy_from_slice(&keys[32..64]); + (client_key, server_key) +} diff --git a/service/kbupd_client/src/protobufs.rs b/service/kbupd_client/src/protobufs.rs new file mode 100644 index 0000000..23cfb34 --- /dev/null +++ b/service/kbupd_client/src/protobufs.rs @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod kbupd_client { + include!(concat!(env!("OUT_DIR"), "/protobufs.kbupd_client.rs")); +} diff --git a/service/kbupd_client/src/protobufs_impl/kbupd_client.rs b/service/kbupd_client/src/protobufs_impl/kbupd_client.rs new file mode 100644 index 0000000..3e75c72 --- /dev/null +++ b/service/kbupd_client/src/protobufs_impl/kbupd_client.rs @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use crate::protobufs::kbupd_client; + +#[derive(Default)] +struct ToHex<'a>(pub &'a [u8]); +impl<'a> ToHex<'a> { + pub fn new>(bytes: &'a T) -> Self { + ToHex(bytes.as_ref()) + } +} +impl<'a> std::fmt::Display for ToHex<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for byte in self.0 { + write!(f, "{:02x}", byte)?; + } + Ok(()) + } +} + +// +// BackupRequest impls +// + +impl std::fmt::Display for kbupd_client::BackupRequest { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "service_id={} ", &self.service_id.as_ref().map(ToHex::new).unwrap_or_default())?; + write!(fmt, "backup_id={} ", &self.backup_id.as_ref().map(ToHex::new).unwrap_or_default())?; + write!(fmt, "token={} ", &self.token.as_ref().map(ToHex::new).unwrap_or_default())?; + write!(fmt, "data={} ", &self.data.as_ref().map(ToHex::new).unwrap_or_default())?; + write!(fmt, "pin={} ", &self.pin.as_ref().map(ToHex::new).unwrap_or_default())?; + write!(fmt, "tries={} ", &self.tries.unwrap_or(0))?; + write!(fmt, "valid_from={}", &self.valid_from.unwrap_or(0))?; + Ok(()) + } +} + +// +// RestoreRequest impls +// + +impl std::fmt::Display for kbupd_client::RestoreRequest { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "service_id={} ", &self.service_id.as_ref().map(ToHex::new).unwrap_or_default())?; + write!(fmt, "backup_id={} ", &self.backup_id.as_ref().map(ToHex::new).unwrap_or_default())?; + write!(fmt, "token={} ", &self.token.as_ref().map(ToHex::new).unwrap_or_default())?; + write!(fmt, "pin={} ", &self.pin.as_ref().map(ToHex::new).unwrap_or_default())?; + write!(fmt, "valid_from={}", &self.valid_from.unwrap_or(0))?; + Ok(()) + } +} + +// +// DeleteRequest impls +// + +impl std::fmt::Display for kbupd_client::DeleteRequest { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "service_id={} ", &self.service_id.as_ref().map(ToHex::new).unwrap_or_default())?; + write!(fmt, "backup_id={}", &self.backup_id.as_ref().map(ToHex::new).unwrap_or_default())?; + Ok(()) + } +} + +// +// BackupResponse impls +// + +impl std::fmt::Display for kbupd_client::BackupResponse { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Some(status) = kbupd_client::backup_response::Status::from_i32(self.status.unwrap_or(0)) { + write!(fmt, "status={:?} ", &status)?; + } else { + write!(fmt, "status={:?} ", &self.status)?; + } + write!(fmt, "token={}", &self.token.as_ref().map(ToHex::new).unwrap_or_default())?; + Ok(()) + } +} + +// +// RestoreResponse impls +// + +impl std::fmt::Display for kbupd_client::RestoreResponse { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Some(status) = kbupd_client::restore_response::Status::from_i32(self.status.unwrap_or(0)) { + write!(fmt, "status={:?} ", &status)?; + } else { + write!(fmt, "status={:?} ", &self.status)?; + } + write!(fmt, "token={} ", &self.token.as_ref().map(ToHex::new).unwrap_or_default())?; + write!(fmt, "data={} ", &self.data.as_ref().map(ToHex::new).unwrap_or_default())?; + write!(fmt, "tries={}", &self.tries.unwrap_or(0))?; + Ok(()) + } +} + +// +// DeleteResponse impls +// + +impl std::fmt::Display for kbupd_client::DeleteResponse { + fn fmt(&self, _fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + Ok(()) + } +} diff --git a/service/kbupd_client/src/protobufs_impl/mod.rs b/service/kbupd_client/src/protobufs_impl/mod.rs new file mode 100644 index 0000000..11eab32 --- /dev/null +++ b/service/kbupd_client/src/protobufs_impl/mod.rs @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +mod kbupd_client; diff --git a/service/kbupd_config/.cargo/config b/service/kbupd_config/.cargo/config new file mode 100644 index 0000000..fe9c8d6 --- /dev/null +++ b/service/kbupd_config/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/target" diff --git a/service/kbupd_config/Cargo.toml b/service/kbupd_config/Cargo.toml new file mode 100644 index 0000000..dee4b92 --- /dev/null +++ b/service/kbupd_config/Cargo.toml @@ -0,0 +1,22 @@ +[package] +authors = ["Open Whisper Systems"] +name = "kbupd_config" +version = "0.1.0" +license = "AGPL-3.0-or-later" +description = "Key Backup Service Daemon Config" +edition = "2018" + +[features] +default = ["clap"] + +[dependencies] +clap = { version = "2.33", optional = true } +kbupd_util = { path = "../kbupd_util" } +serde = "1.0" +serde_derive = "1.0" +serde_yaml = "0.8" + +[[bin]] +name = "kbupd-config" +path = "src/main.rs" +required-features = ["clap"] diff --git a/service/kbupd_config/src/frontend.rs b/service/kbupd_config/src/frontend.rs new file mode 100644 index 0000000..d99ea00 --- /dev/null +++ b/service/kbupd_config/src/frontend.rs @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(non_snake_case)] + +use std::path::{PathBuf}; + +use kbupd_util::hex; +use serde_derive::{Deserialize}; + +use crate::metrics::*; + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FrontendConfig { + pub api: FrontendApiConfig, + + pub attestation: FrontendAttestationConfig, + + pub control: FrontendControlConfig, + + pub metrics: Option, + + pub enclaves: Vec, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FrontendApiConfig { + pub listenHostPort: String, + + #[serde(with = "hex")] + pub userAuthenticationTokenSharedSecret: Vec, + + #[serde(with = "hex")] + pub backupIdSecret: Vec, + + #[serde(default)] + pub limits: FrontendApiRateLimitsConfig, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FrontendApiRateLimitsConfig { + pub token: FrontendRateLimitConfig, + + pub attestation: FrontendRateLimitConfig, + + pub backup: FrontendRateLimitConfig, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FrontendRateLimitConfig { + pub bucketSize: u64, + + pub leakRatePerMinute: f64, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FrontendAttestationConfig { + pub host: String, + + #[serde(with = "hex::SerdeFixedLengthHex")] + pub spid: [u8; 16], + + pub tlsConfigPath: PathBuf, + + #[serde(default)] + pub acceptGroupOutOfDate: bool, + + #[serde(default)] + pub disabled: bool, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FrontendControlConfig { + pub listenHostPort: String, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FrontendEnclaveConfig { + pub name: String, + + pub mrenclave: String, + + pub debug: bool, + + pub electionTimeoutMs: u64, + + pub pendingRequestCount: u32, + + pub pendingRequestTtlMs: u64, + + pub partitions: Vec, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FrontendPartitionConfig { + pub range: Option, + + pub replicas: Vec, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FrontendPartitionRangeConfig { + #[serde(with = "hex::SerdeFixedLengthHex")] + pub firstBackupId: [u8; 32], + + #[serde(with = "hex::SerdeFixedLengthHex")] + pub lastBackupId: [u8; 32], +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct FrontendPartitionReplicaConfig { + pub hostPort: String, +} + +// +// FrontendApiRateLimitsConfig impls +// + +impl Default for FrontendApiRateLimitsConfig { + fn default() -> Self { + Self { + token: FrontendRateLimitConfig { + bucketSize: 10, + leakRatePerMinute: 10.0, + }, + attestation: FrontendRateLimitConfig { + bucketSize: 10, + leakRatePerMinute: 10.0, + }, + backup: FrontendRateLimitConfig { + bucketSize: 10, + leakRatePerMinute: 10.0, + }, + } + } +} diff --git a/service/kbupd_config/src/lib.rs b/service/kbupd_config/src/lib.rs new file mode 100644 index 0000000..c49c397 --- /dev/null +++ b/service/kbupd_config/src/lib.rs @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod frontend; +pub mod metrics; +pub mod replica; + +pub use frontend::{FrontendConfig}; +pub use replica::{ReplicaConfig}; diff --git a/service/kbupd_config/src/main.rs b/service/kbupd_config/src/main.rs new file mode 100644 index 0000000..0536d70 --- /dev/null +++ b/service/kbupd_config/src/main.rs @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fs; +use std::path::{Path}; + +use kbupd_config::*; + +fn main() { + let arguments = parse_arguments(); + + let (subcommand_name, subcommand_arguments) = arguments.subcommand(); + let subcommand_arguments = subcommand_arguments.unwrap(); + + match subcommand_name { + "validate" => { + match validate(&subcommand_arguments) { + Ok(()) => (), + Err(()) => std::process::exit(1), + } + } + _ => unreachable!(), + } +} + +fn validate(arguments: &clap::ArgMatches<'static>) -> Result<(), ()> { + let (subcommand_name, subcommand_arguments) = arguments.subcommand(); + let subcommand_arguments = subcommand_arguments.unwrap(); + + let mut result = Ok(()); + + let config_file_paths = subcommand_arguments.values_of("config_file").expect("no config_file").map(Path::new); + for config_file_path in config_file_paths { + let config_file = match fs::File::open(&config_file_path) { + Ok(config_file) => config_file, + Err(error) => { + eprintln!("error opening config file {}: {}", config_file_path.display(), error); + continue + } + }; + + let parse_result = match subcommand_name { + "frontend" => { + serde_yaml::from_reader::<_, FrontendConfig>(config_file).map(drop) + } + "replica" => { + serde_yaml::from_reader::<_, ReplicaConfig>(config_file).map(drop) + } + _ => unreachable!(), + }; + + match parse_result { + Ok(()) => eprintln!("parsed config file {}", config_file_path.display()), + Err(error) => { + eprintln!("error parsing config file {}: {:?}", config_file_path.display(), error); + result = Err(()); + } + } + } + result +} + +fn parse_arguments() -> clap::ArgMatches<'static> { + let config_file_argument = + clap::Arg::with_name("config_file") + .takes_value(true) + .multiple(true) + .index(1) + .value_name("config_file_path") + .help("Path to YAML config file"); + + let validate_frontend_subcommand = + clap::SubCommand::with_name("frontend") + .arg(config_file_argument.clone()) + .about("validate kbupd frontend YAML config file"); + + let validate_replica_subcommand = + clap::SubCommand::with_name("replica") + .arg(config_file_argument) + .about("validate kbupd replica YAML config file"); + + let config_validate_subcommand = + clap::SubCommand::with_name("validate") + .setting(clap::AppSettings::VersionlessSubcommands) + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .subcommand(validate_frontend_subcommand) + .subcommand(validate_replica_subcommand) + .about("validate kbupd YAML config file"); + + clap::App::new("kbupd-config") + .version(clap::crate_version!()) + .about(format!("{} -- Config Utility", clap::crate_description!()).as_str()) + .author(clap::crate_authors!()) + .setting(clap::AppSettings::VersionlessSubcommands) + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .subcommand(config_validate_subcommand) + .get_matches() +} diff --git a/service/kbupd_config/src/metrics.rs b/service/kbupd_config/src/metrics.rs new file mode 100644 index 0000000..c85b67d --- /dev/null +++ b/service/kbupd_config/src/metrics.rs @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(non_snake_case)] + +use serde_derive::*; + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MetricsConfig { + pub reporters: Vec, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields, tag="type", rename_all="lowercase")] +pub enum MetricsReporterConfig { + Json(JsonMetricsReporterConfig), +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct JsonMetricsReporterConfig { + pub hostname: String, + + pub token: String, + + pub intervalSeconds: Option, +} diff --git a/service/kbupd_config/src/replica.rs b/service/kbupd_config/src/replica.rs new file mode 100644 index 0000000..2fd6ba0 --- /dev/null +++ b/service/kbupd_config/src/replica.rs @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(non_snake_case)] + +use std::path::{PathBuf}; + +use kbupd_util::hex; +use serde_derive::{Deserialize}; + +use crate::metrics::*; + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ReplicaConfig { + pub attestation: ReplicaAttestationConfig, + + pub control: ReplicaControlConfig, + + pub metrics: Option, + + pub enclave: ReplicaEnclaveConfig, +} + +#[derive(Deserialize, Default)] +#[serde(deny_unknown_fields)] +pub struct ReplicaAttestationConfig { + pub host: String, + + #[serde(with = "hex::SerdeFixedLengthHex")] + pub spid: [u8; 16], + + pub tlsConfigPath: PathBuf, + + #[serde(default)] + pub acceptGroupOutOfDate: bool, + + #[serde(default)] + pub disabled: bool, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ReplicaControlConfig { + pub listenHostPort: String, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ReplicaEnclaveConfig { + pub mrenclave: String, + + pub debug: bool, + + pub listenHostPort: String, + + pub maxConnections: u32, + + pub storageSize: u32, + + pub raftLogSize: u64, + + pub electionTimeoutMs: u64, + + pub electionHeartbeats: u32, + + pub replicationChunkSize: u32, + + pub transferChunkSize: u32, + + pub attestationExpiryCommitIntervalMs: u64, + + pub maxFrontendCount: u32, + + #[serde(default)] + pub replicas: Vec, + + pub sourcePartition: Option, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ReplicaSourcePartitionConfig { + #[serde(with = "hex::SerdeFixedLengthHex")] + pub firstBackupId: [u8; 32], + + #[serde(with = "hex::SerdeFixedLengthHex")] + pub lastBackupId: [u8; 32], + + pub replicas: Vec, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ReplicaPeerConfig { + pub hostPort: String, +} diff --git a/service/kbupd_util/.cargo/config b/service/kbupd_util/.cargo/config new file mode 100644 index 0000000..fe9c8d6 --- /dev/null +++ b/service/kbupd_util/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/target" diff --git a/service/kbupd_util/Cargo.toml b/service/kbupd_util/Cargo.toml new file mode 100644 index 0000000..bd1a718 --- /dev/null +++ b/service/kbupd_util/Cargo.toml @@ -0,0 +1,16 @@ +[package] +authors = ["Open Whisper Systems"] +name = "kbupd_util" +version = "0.1.0" +license = "AGPL-3.0-or-later" +description = "Key Backup Service Daemon Utility Library" +edition = "2018" + +[dependencies] +base64 = "0.10" +bytes = "0.4" +hex = "0.4" +rand = "0.6" +serde = "1.0" +serde_derive = "1.0" +regex = "1.1" diff --git a/service/kbupd_util/src/base64.rs b/service/kbupd_util/src/base64.rs new file mode 100644 index 0000000..f3c430f --- /dev/null +++ b/service/kbupd_util/src/base64.rs @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fmt; +use std::marker::{PhantomData}; + +use base64; +use serde::{Deserializer, Serializer}; + +pub fn decode(encoded: &[u8]) -> Result, base64::DecodeError> { + let space_regex = regex::bytes::Regex::new(r"[ \t\r\n]").unwrap(); + let base64_data = space_regex.replace_all(encoded, &b""[..]); + let config = base64::Config::new(base64::CharacterSet::Standard, true); + base64::decode_config(&base64_data, config) +} + +pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + deserializer.deserialize_bytes(Base64Visitor) +} + +pub fn serialize(data: impl AsRef<[u8]>, serializer: S) -> Result { + serializer.serialize_str(&base64::encode(data.as_ref())) +} + +// +// Base64Visitor impls +// + +struct Base64Visitor; + +impl<'de> serde::de::Visitor<'de> for Base64Visitor { + type Value = Vec; + fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str("a base64-encoded string") + } + + fn visit_bytes(self, base64: &[u8]) -> Result + where E: serde::de::Error + { + decode(base64).map_err(|error| E::custom(error.to_string())) + } + + fn visit_str(self, base64: &str) -> Result + where E: serde::de::Error + { + self.visit_bytes(base64.as_bytes()) + } +} + +// +// FixedLengthBase64Visitor impls +// + +struct FixedLengthBase64Visitor(PhantomData); + +impl<'de, T> serde::de::Visitor<'de> for FixedLengthBase64Visitor +where T: AsMut<[u8]> + AsRef<[u8]> + Default +{ + type Value = T; + fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str("a base64-encoded string") + } + + fn visit_bytes(self, base64: &[u8]) -> Result + where E: serde::de::Error + { + let mut deserialized = T::default(); + let estimated_length = (base64.len() + 3) / 4 * 3; + if estimated_length > deserialized.as_ref().len() + 2 { + Err(E::custom(format!("base64 parameter length {} > {}", estimated_length, deserialized.as_ref().len()))) + } else { + let data = decode(base64).map_err(|error| E::custom(error.to_string()))?; + if data.len() != deserialized.as_ref().len() { + Err(E::custom(format!("base64 parameter length {} != {}", data.len(), deserialized.as_ref().len()))) + } else { + deserialized.as_mut().copy_from_slice(&data[..]); + Ok(deserialized) + } + } + } + + fn visit_str(self, base64: &str) -> Result + where E: serde::de::Error + { + self.visit_bytes(base64.as_bytes()) + } +} + +pub trait SerdeFixedLengthBase64: Sized + AsMut<[u8]> + AsRef<[u8]> + Default { + fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + deserializer.deserialize_bytes(FixedLengthBase64Visitor(PhantomData)) + } + + fn serialize(&self, serializer: S) -> Result { + serialize(self, serializer) + } +} + +impl + AsRef<[u8]> + Default> SerdeFixedLengthBase64 for T { +} diff --git a/service/kbupd_util/src/duration.rs b/service/kbupd_util/src/duration.rs new file mode 100644 index 0000000..206140f --- /dev/null +++ b/service/kbupd_util/src/duration.rs @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::time::{Duration}; + +use rand::Rng; + +pub const NANOS_PER_SEC: u32 = 1_000_000_000; + +pub fn random(max: Duration) -> Duration { + let secs = rand::thread_rng().gen_range(0, max.as_secs().saturating_add(1)); + let nanos = rand::thread_rng().gen_range(0, max.subsec_nanos().saturating_add(1)); + Duration::new(secs, nanos) +} + +pub fn as_ticks(duration: Duration, tick_interval: Duration) -> u32 { + let duration_ms = duration.as_millis(); + let tick_interval_ms = tick_interval.as_millis(); + let ticks = duration_ms.saturating_add(tick_interval_ms.saturating_sub(1)) + .checked_div(tick_interval_ms) + .unwrap_or(0); + ticks as u32 +} + +pub fn as_secs_f64(duration: Duration) -> f64 { + (duration.as_secs() as f64) + (duration.subsec_nanos() as f64) / (NANOS_PER_SEC as f64) +} + +#[cfg(test)] +mod test { + use std::time::{Duration}; + + use super::*; + + #[test] + fn test_as_ticks() { + let max_duration = Duration::new(u64::max_value(), NANOS_PER_SEC - 1); + + assert_eq!(as_ticks(Duration::from_secs(10), Duration::from_secs(1)), 10); + assert_eq!(as_ticks(Duration::from_secs(10), Duration::from_secs(0)), 0); + assert_eq!(as_ticks(Duration::from_millis(100), Duration::from_millis(10)), 10); + assert_eq!(as_ticks(Duration::from_millis(100), Duration::from_millis(11)), 10); + assert_eq!(as_ticks(Duration::from_millis(100), Duration::from_millis(12)), 9); + assert_eq!(as_ticks(Duration::from_millis(100), Duration::from_millis(99)), 2); + assert_eq!(as_ticks(Duration::from_millis(100), Duration::from_millis(100)), 1); + assert_eq!(as_ticks(Duration::from_millis(100), Duration::from_millis(1000)), 1); + + assert_eq!(as_ticks(max_duration, Duration::from_secs(0)), 0); + assert_eq!(as_ticks(max_duration, max_duration), 1); + assert_eq!(as_ticks(Duration::from_secs(0), max_duration), 0); + assert_eq!(as_ticks(Duration::from_secs(0), Duration::from_secs(0)), 0); + + assert_eq!(as_ticks(Duration::from_millis(1), max_duration), 1); + } +} diff --git a/service/kbupd_util/src/hex.rs b/service/kbupd_util/src/hex.rs new file mode 100644 index 0000000..ce25a42 --- /dev/null +++ b/service/kbupd_util/src/hex.rs @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fmt; +use std::marker::{PhantomData}; + +use serde::{Deserializer, Serializer}; + +use super::{ToHex}; + +pub fn parse(hex: &str) -> Result, hex::FromHexError> { + hex::decode(hex) +} + +pub fn parse_fixed(hex: &str) -> Result +where T: Sized + AsMut<[u8]> + AsRef<[u8]> + Default +{ + let mut bytes = T::default(); + let () = hex::decode_to_slice(hex, bytes.as_mut())?; + Ok(bytes) +} + +pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + deserializer.deserialize_str(HexVisitor) +} + +pub fn serialize(data: impl AsRef<[u8]>, serializer: S) -> Result { + serializer.serialize_str(&format!("{}", ToHex(data.as_ref()))) +} + +// +// HexVisitor impls +// + +struct HexVisitor; + +impl<'de> serde::de::Visitor<'de> for HexVisitor { + type Value = Vec; + fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str("a hexadecimal-encoded string") + } + fn visit_str(self, hex: &str) -> Result, E> + where E: serde::de::Error + { + parse(hex).map_err(|error| E::custom(format!("{}", error))) + } +} + +// +// FixedLengthHexVisitor impls +// + +struct FixedLengthHexVisitor(PhantomData); + +impl<'de, T> serde::de::Visitor<'de> for FixedLengthHexVisitor +where T: AsMut<[u8]> + AsRef<[u8]> + Default +{ + type Value = T; + fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str("a hexadecimal-encoded string") + } + fn visit_str(self, hex: &str) -> Result + where E: serde::de::Error + { + parse_fixed(hex).map_err(|error| E::custom(format!("{}", error))) + } +} + +pub trait SerdeFixedLengthHex: Sized + AsMut<[u8]> + AsRef<[u8]> + Default { + fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + deserializer.deserialize_str(FixedLengthHexVisitor(PhantomData)) + } + + fn serialize(&self, serializer: S) -> Result { + serialize(self, serializer) + } +} + +impl + AsRef<[u8]> + Default> SerdeFixedLengthHex for T { +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_parse() { + assert_eq!(&parse("").unwrap(), b""); + assert_eq!(&parse("616263").unwrap(), b"abc"); + assert_eq!(&parse("00fF").unwrap(), b"\x00\xFF"); + + parse("\n").unwrap_err(); + parse(" ").unwrap_err(); + parse(" 00").unwrap_err(); + parse("00 ").unwrap_err(); + parse("00\n").unwrap_err(); + parse(" 00 ").unwrap_err(); + parse("0 0").unwrap_err(); + parse("0").unwrap_err(); + parse("0g").unwrap_err(); + parse("0\x00").unwrap_err(); + parse("\x00").unwrap_err(); + parse("\x00\x00").unwrap_err(); + parse("FF\x7F").unwrap_err(); + parse("000").unwrap_err(); + } + + #[test] + fn test_parse_fixed() { + assert_eq!(&parse_fixed::<[u8; 0]>("").unwrap(), b""); + assert_eq!(&parse_fixed::<[u8; 3]>("616263").unwrap(), b"abc"); + assert_eq!(&parse_fixed::<[u8; 2]>("00fF").unwrap(), b"\x00\xFF"); + + parse_fixed::<[u8; 1]>("").unwrap_err(); + parse_fixed::<[u8; 0]>("00").unwrap_err(); + parse_fixed::<[u8; 2]>("00").unwrap_err(); + + macro_rules! test_parse_fixed { + ($n:literal) => ({ + parse_fixed::<[u8; $n]>("\n").unwrap_err(); + parse_fixed::<[u8; $n]>(" ").unwrap_err(); + parse_fixed::<[u8; $n]>(" 00").unwrap_err(); + parse_fixed::<[u8; $n]>("00 ").unwrap_err(); + parse_fixed::<[u8; $n]>("00\n").unwrap_err(); + parse_fixed::<[u8; $n]>(" 00 ").unwrap_err(); + parse_fixed::<[u8; $n]>("0 0").unwrap_err(); + parse_fixed::<[u8; $n]>("0").unwrap_err(); + parse_fixed::<[u8; $n]>("0g").unwrap_err(); + parse_fixed::<[u8; $n]>("0\x00").unwrap_err(); + parse_fixed::<[u8; $n]>("\x00").unwrap_err(); + parse_fixed::<[u8; $n]>("\x00\x00").unwrap_err(); + parse_fixed::<[u8; $n]>("FF\x7F").unwrap_err(); + parse_fixed::<[u8; $n]>("000").unwrap_err(); + }) + } + test_parse_fixed!(0); + test_parse_fixed!(1); + test_parse_fixed!(2); + } +} diff --git a/service/kbupd_util/src/lib.rs b/service/kbupd_util/src/lib.rs new file mode 100644 index 0000000..56027d5 --- /dev/null +++ b/service/kbupd_util/src/lib.rs @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod base64; +pub mod duration; +pub mod hex; +pub mod pem; +pub mod percent; +pub mod thread; + +use std::fmt; +use std::io; +use std::net::{ToSocketAddrs, SocketAddr}; + +pub struct ToHex<'a>(pub &'a [u8]); +pub struct OptionDisplay(pub Option); +pub struct ListDisplay(pub T); +pub struct DisplayAsDebug(pub T); + +pub enum Never {} + +pub fn to_socket_addr(address: impl ToSocketAddrs) -> io::Result { + address.to_socket_addrs()? + .next() + .ok_or(io::Error::new(io::ErrorKind::Other, "empty listen address")) +} + +// +// ToHex impls +// + +impl<'a> ToHex<'a> { + pub fn new>(bytes: &'a T) -> Self { + ToHex(bytes.as_ref()) + } +} + +impl<'a> fmt::Display for ToHex<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let ToHex(data) = self; + for byte in *data { + write!(fmt, "{:02x}", byte)?; + } + Ok(()) + } +} +impl<'a> fmt::Debug for ToHex<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +impl fmt::Display for OptionDisplay +where T: fmt::Display, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let OptionDisplay(inner) = self; + match inner { + Some(inner) => fmt::Display::fmt(inner, fmt), + None => write!(fmt, ""), + } + } +} + +// +// OptionDisplay impls +// + +impl fmt::Debug for OptionDisplay +where T: fmt::Display, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +// +// ListDisplay impls +// + +impl fmt::Display for ListDisplay +where T: IntoIterator + Clone, + T::Item: fmt::Display, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let ListDisplay(inner) = self; + fmt.debug_list() + .entries(inner.clone().into_iter().map(DisplayAsDebug)) + .finish() + } +} + +impl fmt::Debug for ListDisplay +where T: IntoIterator + Clone, + T::Item: fmt::Display, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, fmt) + } +} + +// +// DisplayAsDebug impls +// + +impl fmt::Debug for DisplayAsDebug +where T: fmt::Display, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let DisplayAsDebug(inner) = self; + fmt::Display::fmt(inner, fmt) + } +} + +// +// Never impls +// + +macro_rules! from_never { + ($type:ty) => ( + impl From for $type { + fn from(never: Never) -> Self { + match never {} + } + } + ) +} + +from_never!(()); diff --git a/service/kbupd_util/src/pem.rs b/service/kbupd_util/src/pem.rs new file mode 100644 index 0000000..e581b31 --- /dev/null +++ b/service/kbupd_util/src/pem.rs @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::str; + +pub fn decode(pem: &[u8]) -> Vec> { + // based on RFC7468 + let re = regex::bytes::Regex::new(r"(?-u)-----BEGIN (?:[\x21-\x2C\x2E-\x7E](?:[- ]?[\x21-\x2C\x2E-\x7E])*)?-----[ \t]*(?:\r|\n|\r\n)[ \t\r\n]*([ \t\r\n\x2B\x2F\x30-\x39\x3D\x41-\x5A\x61-\x7A]*)(?:\r|\n|\r\n)-----END (?:[\x21-\x2C\x2E-\x7E](?:[- ]?[\x21-\x2C\x2E-\x7E])*)?-----").unwrap(); + + let mut certificates: Vec> = Vec::new(); + for captures in re.captures_iter(pem) { + if let Ok(der) = crate::base64::decode(&captures[1]) { + if !der.is_empty() { + certificates.push(der); + } + } + } + certificates +} + +pub fn encode(tag: &str, certificates_der: impl IntoIterator + Clone) -> String +where T: AsRef<[u8]> +{ + const PEM_BEGIN: &'static str = "-----BEGIN "; + const PEM_END: &'static str = "-----END "; + const PEM_TAG_SUFFIX: &'static str = "-----\n"; + + let config = base64::Config::new(base64::CharacterSet::Standard, true); + + let mut approx_len = 0; + for certificate_der in certificates_der.clone() { + let unwrapped_len = certificate_der.as_ref().len() * 4 / 3 + 4; + approx_len += PEM_BEGIN.len() + tag.len() + PEM_TAG_SUFFIX.len(); + approx_len += unwrapped_len + (unwrapped_len / 64); + approx_len += PEM_END.len() + tag.len() + PEM_TAG_SUFFIX.len(); + } + + let mut encoded = String::with_capacity(approx_len); + for certificate_der in certificates_der { + encoded.push_str(PEM_BEGIN); + encoded.push_str(tag); + encoded.push_str(PEM_TAG_SUFFIX); + + let base64: String = base64::encode_config(certificate_der.as_ref(), config); + let base64_bytes: &[u8] = base64.as_ref(); + for base64_line_bytes in base64_bytes.chunks(64) { + let base64_line_str = str::from_utf8(base64_line_bytes) + .unwrap_or_else(|_| unreachable!("base64 is ascii")); + encoded.push_str(base64_line_str); + encoded.push_str("\n"); + } + + encoded.push_str(PEM_END); + encoded.push_str(tag); + encoded.push_str(PEM_TAG_SUFFIX); + } + encoded +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_decode() { + assert!(decode(b"").is_empty()); + assert!(decode(b"test").is_empty()); + // malformed data + assert!(decode(b"-----BEGIN TEST-----\n.GVzdA==\n-----END TEST-----").is_empty()); + // malformed tag + assert!(decode(b"-----BEGIN TEST------\ndGVzdA==\n-----END TEST-----").is_empty()); + assert!(decode(b"-----BEGIN TEST -----\ndGVzdA==\n-----END TEST-----").is_empty()); + assert!(decode(b"-----BEGIN TEST-----\ndGVzdA==\n-----END TEST -----").is_empty()); + // missing line break + assert!(decode(b"-----BEGIN TEST -----dGVzdA==\n-----END TEST-----").is_empty()); + assert!(decode(b"-----BEGIN TEST -----\ndGVzdA==-----END TEST-----").is_empty()); + // missing data + assert!(decode(b"-----BEGIN TEST-----\n\n-----END TEST-----").is_empty()); + assert!(decode(b"-----BEGIN TEST-----\n \n-----END TEST-----").is_empty()); + // valid tests + assert_eq!(decode(b"-----BEGIN -----\ndGVzdA==\n-----END -----"), [b"test"]); + assert_eq!(decode(b"-----BEGIN -----\t \r\n dGVzdA== \n\n-----END TEST-----"), [b"test"]); + assert_eq!(decode(b"-----BEGIN TEST-----\t \r\n dGVzdA== \n\n-----END TEST-----"), [b"test"]); + assert_eq!(decode(b"-----BEGIN TEST1 TEST2-----\t \r\n d\tG VzdA== \n\n-----END TEST1-TEST2-----"), [b"test"]); + let test_certs = [b"test"]; + assert_eq!(decode(encode("", &test_certs).as_bytes()), test_certs); + let test_certs = [b"test1", b"test2"]; + assert_eq!(decode(encode("SOME TAG", &test_certs).as_bytes()), test_certs); + } + + #[test] + fn test_encode() { + let no_certs: [&'static [u8]; 0] = []; + assert_eq!(encode("TEST", &no_certs), ""); + assert_eq!(encode("", &[b"test"]), "-----BEGIN -----\ndGVzdA==\n-----END -----\n"); + assert_eq!(encode("TEST", &[b""]), "-----BEGIN TEST-----\n-----END TEST-----\n"); + assert_eq!(encode("TEST", &[b"test1", b"test2"]), + "-----BEGIN TEST-----\ndGVzdDE=\n-----END TEST-----\n-----BEGIN TEST-----\ndGVzdDI=\n-----END TEST-----\n"); + } +} diff --git a/service/kbupd_util/src/percent.rs b/service/kbupd_util/src/percent.rs new file mode 100644 index 0000000..745a93c --- /dev/null +++ b/service/kbupd_util/src/percent.rs @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::str; + +pub fn decode(encoded: &[u8]) -> Vec { + let re = regex::bytes::Regex::new(r"(?-u)%(?:([\x30-\x39\x41-\x46\x61-\x66]{2})|(%))").unwrap(); + + let mut decoded: Vec = Vec::with_capacity(encoded.len()); + let mut last_match_end = 0; + for capture in re.captures_iter(encoded) { + if let Some(capture_match) = capture.get(1) { + decoded.extend(&encoded[last_match_end..(capture_match.start() - 1)]); + decoded.push(u8::from_str_radix(str::from_utf8(capture_match.as_bytes()).unwrap(), 16).unwrap()); + last_match_end = capture_match.end(); + } else if let Some(capture_match) = capture.get(2) { + decoded.extend(&encoded[last_match_end..capture_match.start()]); + last_match_end = capture_match.end(); + } + } + decoded.extend(&encoded[last_match_end..]); + decoded +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_decode() { + // valid tests + assert_eq!(decode(b""), b""); + assert_eq!(decode(b"%20"), b" "); + assert_eq!(decode(b"%%"), b"%"); + assert_eq!(decode(b"ABCD"), b"ABCD"); + assert_eq!(decode(b"%41BC"), b"ABC"); + assert_eq!(decode(b"AB%%%43"), b"AB%C"); + assert_eq!(decode(b"test1%20test2"), b"test1 test2"); + assert_eq!(decode(b"test1%20%20test2"), b"test1 test2"); + assert_eq!(decode(b"%00%FF"), b"\x00\xFF"); + // invalid hex + assert_eq!(decode(b"%"), b"%"); + assert_eq!(decode(b"%A"), b"%A"); + assert_eq!(decode(b"%%A"), b"%A"); + assert_eq!(decode(b"%%%"), b"%%"); + assert_eq!(decode(b"%AZ"), b"%AZ"); + assert_eq!(decode(b"AB%C"), b"AB%C"); + assert_eq!(decode(b"AB%C%D"), b"AB%C%D"); + assert_eq!(decode(b"AB%C%44"), b"AB%CD"); + } +} diff --git a/service/kbupd_util/src/thread.rs b/service/kbupd_util/src/thread.rs new file mode 100644 index 0000000..ae03edd --- /dev/null +++ b/service/kbupd_util/src/thread.rs @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::thread; +use std::thread::{JoinHandle}; +use std::time::*; +use std::sync::*; + +#[derive(Clone)] +pub struct StopJoinHandle { + stop_state: Arc, + join_handle: Arc>>>, +} + +#[derive(Default)] +pub struct StopState { + condvar: Condvar, + stopped: Mutex, +} + +impl StopJoinHandle { + pub fn new(stop_state: Arc, join_handle: JoinHandle) -> Self { + Self { + stop_state, + join_handle: Arc::new(Mutex::new(Some(join_handle))), + } + } + pub fn stop(&self) { + let mut stopped_guard = match self.stop_state.stopped.lock() { + Ok(guard) => guard, + Err(poison) => poison.into_inner(), + }; + *stopped_guard = true; + self.stop_state.condvar.notify_all(); + } + pub fn join(&self) -> Option> { + let mut join_handle_guard = match self.join_handle.lock() { + Ok(guard) => guard, + Err(poison) => poison.into_inner(), + }; + if let Some(join_handle) = join_handle_guard.take() { + Some(join_handle.join()) + } else { + None + } + } +} +impl StopState { + pub fn sleep_while_running(&self, duration: Duration) -> bool { + let mut stopped_guard = match self.stopped.lock() { + Ok(guard) => guard, + Err(poison) => poison.into_inner(), + }; + let start = Instant::now(); + loop { + if *stopped_guard { + break false; + } + let timeout = match duration.checked_sub(start.elapsed()) { + Some(timeout) => timeout, + None => break true, + }; + stopped_guard = { + let (stopped_guard, wait_timeout_result) = match self.condvar.wait_timeout(stopped_guard, timeout) { + Ok(result) => result, + Err(poison) => poison.into_inner(), + }; + if wait_timeout_result.timed_out() { + break !*stopped_guard; + } else { + stopped_guard + } + }; + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_stop_join() { + let stop_state = Arc::new(StopState::default()); + let stop_state_2 = stop_state.clone(); + let join_handle = std::thread::spawn(move || { + assert!(!stop_state.sleep_while_running(Duration::from_secs(60))); + }); + let stop_join_handle = StopJoinHandle::new(stop_state_2, join_handle); + stop_join_handle.stop(); + let () = stop_join_handle.join().unwrap().unwrap(); + assert!(stop_join_handle.join().is_none()); + } + + #[test] + fn test_sleep_while_running() { + let stop_state = Arc::new(StopState::default()); + let stop_state_2 = stop_state.clone(); + let (tx, rx) = std::sync::mpsc::channel(); + let join_handle = std::thread::spawn(move || { + assert!(stop_state.sleep_while_running(Duration::from_millis(1))); + assert!(stop_state.sleep_while_running(Duration::from_millis(1))); + let _ = tx.send(()); + assert!(!stop_state.sleep_while_running(Duration::from_secs(60))); + }); + let stop_join_handle = StopJoinHandle::new(stop_state_2, join_handle); + let () = rx.recv().unwrap(); + stop_join_handle.stop(); + let () = stop_join_handle.join().unwrap().unwrap(); + } +} diff --git a/service/kbuptlsd/.cargo/config b/service/kbuptlsd/.cargo/config new file mode 100644 index 0000000..fe9c8d6 --- /dev/null +++ b/service/kbuptlsd/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/target" diff --git a/service/kbuptlsd/Cargo.toml b/service/kbuptlsd/Cargo.toml new file mode 100644 index 0000000..70e0f3d --- /dev/null +++ b/service/kbuptlsd/Cargo.toml @@ -0,0 +1,30 @@ +[package] +authors = ["Open Whisper Systems"] +name = "kbuptlsd" +version = "1.0.1" +license = "AGPL-3.0-or-later" +description = "Key Backup Service TLS Unwrapper Daemon" +edition = "2018" + +[dependencies] +base64 = "0.10" +chrono = "0.4" +clap = "2.33" +failure = "0.1" +futures = "0.1" +hyper = { version = "0.12", optional = true } +libc = "0.2" +log = { version = "0.4", features = ["std"] } +nix = "0.13" +mio = "0.6" +regex = "1.1" +serde = "1.0" +serde_derive = "1.0" +serde_yaml = "0.8" +tk-listen = "0.2" +tokio = "0.1" +rustunnel = { path = "../rustunnel" } + +[dev-dependencies] +rand_core = "0.5" +rand_chacha = "0.2" diff --git a/service/kbuptlsd/src/base64.rs b/service/kbuptlsd/src/base64.rs new file mode 100644 index 0000000..0cd1747 --- /dev/null +++ b/service/kbuptlsd/src/base64.rs @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fmt; + +use base64; +use serde::{Deserializer}; + +pub fn decode(encoded: &[u8]) -> Result, base64::DecodeError> { + let space_regex = regex::bytes::Regex::new(r"[ \t\r\n]").unwrap(); + let base64_data = space_regex.replace_all(encoded, &b""[..]); + let config = base64::Config::new(base64::CharacterSet::Standard, true); + base64::decode_config(&base64_data, config) +} + +pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + deserializer.deserialize_bytes(Base64Visitor) +} + +// +// Base64Visitor impls +// + +struct Base64Visitor; + +impl<'de> serde::de::Visitor<'de> for Base64Visitor { + type Value = Vec; + fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str("a base64-encoded string") + } + + fn visit_bytes(self, base64: &[u8]) -> Result + where E: serde::de::Error + { + decode(base64).map_err(|error| E::custom(error.to_string())) + } + + fn visit_str(self, base64: &str) -> Result + where E: serde::de::Error + { + self.visit_bytes(base64.as_bytes()) + } +} diff --git a/service/kbuptlsd/src/client.rs b/service/kbuptlsd/src/client.rs new file mode 100644 index 0000000..f8856fd --- /dev/null +++ b/service/kbuptlsd/src/client.rs @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#[cfg(feature = "hyper")] +pub mod hyper; + +use std::io; +use std::fs; +use std::net::{SocketAddr}; +use std::os::unix::prelude::*; +use std::path::{PathBuf}; +use std::process::{Command}; + +use failure::{format_err, ResultExt}; + +use crate::config::*; +use crate::proxy_child::{TlsProxyChild}; +use crate::util::{CommandExt}; + +pub struct TlsClientProxySpawner { + bin_path: PathBuf, + arguments: TlsClientProxyArguments, +} + +pub enum TlsClientProxyArguments { + Config { + config_file: PathBuf, + key_file: Option, + }, + NoConfig { + ca: TlsClientProxyCaArgument, + key_file: Option, + hostname: TlsClientProxyHostnameArgument, + } +} + +pub enum TlsClientProxyCaArgument { + System, + CustomPemFile(PathBuf), +} + +pub enum TlsClientProxyHostnameArgument { + AllowInvalid, + Hostname(String), +} + +// +// TlsClientProxySpawner impls +// + +impl TlsClientProxySpawner { + pub fn new(bin_path: PathBuf, arguments: TlsClientProxyArguments) -> Result { + bin_path.metadata().with_context(|_| format!("error opening bin file {}", bin_path.display()))?; + + match &arguments { + TlsClientProxyArguments::Config { config_file: config_file_path, key_file } => { + let config_file = fs::File::open(config_file_path).with_context(|_| format_err!("error opening config file {}", config_file_path.display()))?; + let config = Config::from_reader(config_file).with_context(|_| format_err!("error reading config file {}", config_file_path.display()))?; + let client_config = config.client.ok_or_else(|| format_err!("config file has no client section: {}", config_file_path.display()))?; + if client_config.clientCertificatePkcs12.is_none() { + let key_file = key_file.as_ref().ok_or_else(|| format_err!("config file has no clientCertificatePkcs12: {}", config_file_path.display()))?; + key_file.metadata().with_context(|_| format!("error opening key file {}", key_file.display()))?; + } + } + TlsClientProxyArguments::NoConfig { ca, key_file, hostname: _ } => { + match ca { + TlsClientProxyCaArgument::System => (), + TlsClientProxyCaArgument::CustomPemFile(ca_file) => { + ca_file.metadata().with_context(|_| format!("error opening ca certificate file {}", ca_file.display()))?; + } + } + if let Some(key_file) = key_file { + key_file.metadata().with_context(|_| format!("error opening key file {}", key_file.display()))?; + } + } + } + + Ok(Self { bin_path, arguments }) + } + + pub fn spawn(&self, + target_stream: impl AsRawFd, + target_address: SocketAddr) + -> Result + { + let mut child = Command::new(&self.bin_path); + child.stdin(std::process::Stdio::piped()); + child.stdout(std::process::Stdio::piped()); + child.stderr(std::process::Stdio::piped()); + + child.preserve_fd(&target_stream); + + if log::max_level() >= log::Level::Debug { + child.arg("--debug"); + } + + child.arg("client"); + match &self.arguments { + TlsClientProxyArguments::Config { config_file, key_file } => { + child.arg("--config-file").arg(config_file); + if let Some(key_file) = key_file { + child.arg("--key-file").arg(key_file); + } + } + TlsClientProxyArguments::NoConfig { ca, key_file, hostname } => { + match ca { + TlsClientProxyCaArgument::System => child.arg("--ca-system"), + TlsClientProxyCaArgument::CustomPemFile(ca_file) => child.arg("--ca-file").arg(ca_file), + }; + if let Some(key_file) = key_file { + child.arg("--key-file").arg(key_file); + } + match hostname { + TlsClientProxyHostnameArgument::AllowInvalid => child.arg("--allow-invalid-target-hostname"), + TlsClientProxyHostnameArgument::Hostname(hostname) => child.arg("--target-hostname").arg(hostname), + }; + } + } + child.arg("--target-fd").arg(target_stream.as_raw_fd().to_string()); + + let child = child.spawn()?; + + Ok(TlsProxyChild::new(child, target_address)) + } +} diff --git a/service/kbuptlsd/src/client/hyper.rs b/service/kbuptlsd/src/client/hyper.rs new file mode 100644 index 0000000..9c54e32 --- /dev/null +++ b/service/kbuptlsd/src/client/hyper.rs @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::sync::*; + +use futures::prelude::*; +use futures::{try_ready}; +use ::hyper::client::connect::{Connect, Connected, Destination}; +use log::{warn, debug, log}; +use tokio::net::{TcpStream}; + +use super::*; +use crate::child; +use crate::proxy_child::*; + +pub struct TlsProxyConnector { + spawner: Arc, + connector: T, +} + +pub struct TlsProxyConnecting { + spawner: Arc, + connect: T::Future, +} + +impl TlsProxyConnector { + pub fn new(spawner: Arc, connector: T) -> Self { + Self { spawner, connector } + } +} + +impl Connect for TlsProxyConnector +where T: Connect, + T::Future: Send, + io::Error: From<::Error>, +{ + type Transport = TlsProxyStream; + type Error = io::Error; + type Future = TlsProxyConnecting; + + fn connect(&self, dst: Destination) -> Self::Future { + Self::Future { + spawner: Arc::clone(&self.spawner), + connect: self.connector.connect(dst), + } + } +} + +impl Future for TlsProxyConnecting +where T: Connect, + io::Error: From, +{ + type Item = (TlsProxyStream, Connected); + type Error = io::Error; + + fn poll(&mut self) -> Poll { + let (tcp_stream, _info) = try_ready!(self.connect.poll()); + let peer_addr = tcp_stream.peer_addr()?; + let (stdio, stderr) = self.spawner.spawn(tcp_stream, peer_addr)?.into_streams()?; + + tokio::spawn(log_proxy_stderr(stderr, peer_addr)); + + Ok(Async::Ready((stdio, Connected::new()))) + } +} + +fn log_proxy_stderr(stderr_stream: TlsProxyStderrStream, address: SocketAddr) -> impl Future { + let logger = stderr_stream.for_each(move |line: String| { + let (mut log_level, line) = child::logger::parse_line(&line); + // we don't want noisy INFO logs for hyper + if log_level == log::Level::Info { + log_level = log::Level::Debug; + } + log!(target: "kbuptlsd::child", log_level, "{} => {}", address, line); + Ok(()) + }); + logger.then(move |result: Result<(), io::Error>| { + match result { + Ok(()) => { + debug!("{} => child process died", address); + Ok(()) + } + Err(error) => { + warn!("{} => error reading from child stderr: {}", address, error); + Err(()) + } + } + }) +} diff --git a/service/kbuptlsd/src/config.rs b/service/kbuptlsd/src/config.rs new file mode 100644 index 0000000..4ad1329 --- /dev/null +++ b/service/kbuptlsd/src/config.rs @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(non_snake_case)] + +use std::io::{Read}; + +use serde_derive::{Deserialize}; +use serde_yaml; + +use crate::base64; + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + pub client: Option, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ClientConfig { + pub clientCertificatePkcs12: Option, + + pub caCertificates: Vec, + + pub hostnameValidation: ClientHostnameValidationConfig, +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Base64ConfigValue( + #[serde(with = "base64")] + pub Vec +); + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ClientCaCertificateConfig { + System, + CustomPem(String), +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ClientHostnameValidationConfig { + AcceptInvalid, + Hostname(String), +} + +// +// Config impls +// + +impl Config { + pub fn from_reader(reader: impl Read) -> Result { + Ok(serde_yaml::from_reader(reader)?) + } +} diff --git a/service/kbuptlsd/src/counter.rs b/service/kbuptlsd/src/counter.rs new file mode 100644 index 0000000..6ecabb1 --- /dev/null +++ b/service/kbuptlsd/src/counter.rs @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::sync::{Arc, Weak}; + +#[derive(Clone, Default)] +pub struct AtomicCounter { + counter: Arc<()>, +} + +pub struct AtomicCounterGuard { + _counter: Weak<()>, +} + +// +// AtomicCounter impls +// + +impl AtomicCounter { + pub fn inc(&self) -> AtomicCounterGuard { + let counter = Arc::downgrade(&self.counter); + AtomicCounterGuard { _counter: counter } + } + pub fn count(&self) -> usize { + Arc::weak_count(&self.counter) + } +} diff --git a/service/kbuptlsd/src/lib.rs b/service/kbuptlsd/src/lib.rs new file mode 100644 index 0000000..ddb7d92 --- /dev/null +++ b/service/kbuptlsd/src/lib.rs @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(unused_parens)] + +pub use rustunnel as child; + +mod base64; +pub mod client; +pub mod config; +pub mod counter; +pub mod proxy_child; +pub mod server; +pub mod util; + +pub mod prelude { + pub use crate::client::{TlsClientProxySpawner, TlsClientProxyArguments, TlsClientProxyCaArgument, TlsClientProxyHostnameArgument}; + #[cfg(feature = "hyper")] + pub use crate::client::hyper::{TlsProxyConnector}; + pub use crate::server::{TlsProxyListener, TlsProxyListenerArguments}; + pub use crate::proxy_child::{TlsProxyChild, TlsProxyStream, TlsProxyStderrStream}; +} diff --git a/service/kbuptlsd/src/main.rs b/service/kbuptlsd/src/main.rs new file mode 100644 index 0000000..80dbeff --- /dev/null +++ b/service/kbuptlsd/src/main.rs @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::fs; +use std::env; +use std::net::{TcpStream}; +use std::num::{NonZeroI32}; +use std::path::{Path}; +use std::process; +use std::os::unix::prelude::*; + +use failure::{format_err, ResultExt}; +use kbuptlsd::config::*; +use rustunnel as child; +use rustunnel::{ClientChild, Identity, ServerChild}; +use rustunnel::stream::{ProxyPipeStream, ProxyTcpStream}; +use rustunnel::tls::{TlsHostname}; +use log::{error, warn, debug}; + +fn main() { + match child::seccomp::configure_malloc() { + Ok(()) => (), + Err(error) => { + eprintln!("error setting up malloc for seccomp: {}", error); + process::exit(1); + } + } + + let arguments = parse_arguments(); + + let log_level = if arguments.is_present("debug") { + log::Level::Debug + } else { + log::Level::Info + }; + log::set_max_level(log_level.to_level_filter()); + + let exit_code = match run(&arguments, log_level) { + Ok(exit_code) => exit_code, + Err(error) => { + error!("initialization error: {:?}", error); + 1 + } + }; + if let Some(error_code) = NonZeroI32::new(exit_code) { + process::exit(error_code.get()); + } +} + +fn run(arguments: &clap::ArgMatches<'static>, log_level: log::Level) -> Result { + match arguments.subcommand() { + ("client", Some(subcommand)) => { + let () = run_client(subcommand, log_level)?; + immediate_exit(0) + } + ("child", Some(subcommand)) => { + let () = run_child(subcommand, log_level)?; + immediate_exit(0) + } + (subcommand_name, _) => { + unreachable!("unknown subcommand {}", subcommand_name); + } + } +} + +fn run_client(arguments: &clap::ArgMatches<'static>, + log_level: log::Level) + -> Result<(), failure::Error> +{ + let logger = child::logger::Logger { level: log_level }; + log::set_boxed_logger(Box::new(logger)).expect("logger already set"); + + let target_fd = match arguments.value_of("target_fd") { + Some(target_fd_str) => Some(target_fd_str.parse::().context("invalid --target-fd")?), + None => None, + }; + + child::seccomp::close_all_fds(&[ + Some(libc::STDIN_FILENO), + Some(libc::STDOUT_FILENO), + Some(libc::STDERR_FILENO), + target_fd, + ].iter().copied().flatten().collect()).context("error closing all fds")?; + + let (tls_hostname, client_cert_p12); + let mut tls_ca_certs = Vec::new(); + if let Some(config_path) = arguments.value_of("config_file") { + let config = read_config(&Path::new(config_path))?; + let client_config = config.client.ok_or_else(|| failure::format_err!("no client config in config file"))?; + client_cert_p12 = client_config.clientCertificatePkcs12; + tls_hostname = match client_config.hostnameValidation { + ClientHostnameValidationConfig::AcceptInvalid => TlsHostname::AcceptInvalid, + ClientHostnameValidationConfig::Hostname(hostname) => TlsHostname::new(hostname), + }; + + for ca_cert_config in client_config.caCertificates { + match ca_cert_config { + ClientCaCertificateConfig::System => { + tls_ca_certs.push(child::tls::CaCertificate::System); + } + ClientCaCertificateConfig::CustomPem(ca_cert_pem) => { + let ca_cert = child::tls::CaCertificate::from_pem(ca_cert_pem.as_bytes()).context("invalid custom ca certificate")?; + tls_ca_certs.push(ca_cert); + } + } + } + } else { + client_cert_p12 = None; + tls_hostname = if !arguments.is_present("allow_invalid_target_hostname") { + TlsHostname::new(arguments.value_of("target_hostname").expect("required argument").to_string()) + } else { + TlsHostname::AcceptInvalid + }; + + if arguments.is_present("ca_system") { + tls_ca_certs.push(child::tls::CaCertificate::System); + } + + for ca_file in arguments.values_of("ca_file").into_iter().flatten() { + let ca_cert_path = Path::new(ca_file); + let ca_cert_pem = fs::read_to_string(ca_cert_path).with_context(|_| format_err!("error reading ca certificate file {}", ca_cert_path.display()))?; + let ca_cert = child::tls::CaCertificate::from_pem(ca_cert_pem.as_bytes()).context("invalid custom ca certificate")?; + tls_ca_certs.push(ca_cert); + } + } + + let maybe_identity = if let Some(key_path) = arguments.value_of("key_file") { + let identity = Identity::from_pkcs12_file(&Path::new(key_path), "").context("invalid client certificate")?; + Some(identity) + } else if let Some(pkcs12_data) = &client_cert_p12 { + let identity = Identity::from_pkcs12(&pkcs12_data.0, "").context("invalid client certificate")?; + Some(identity) + } else { + None + }; + + let source_pipe_stream = match ProxyPipeStream::stdio() { + Ok(source_pipe_stream) => source_pipe_stream, + Err(error) => { + warn!("error setting up source pipe stream: {}", error); + return Ok(()); + } + }; + let target_tcp_stream = if let Some(target_host_port) = arguments.value_of("target_address") { + debug!("connecting to {}", target_host_port); + match TcpStream::connect(target_host_port) { + Ok(target_tcp_stream) => { + debug!("connected to {}", target_host_port); + target_tcp_stream + } + Err(error) => { + warn!("error connecting to {}: {}", target_host_port, error); + return Ok(()); + } + } + } else { + unsafe { TcpStream::from_raw_fd(target_fd.expect("required argument")) } + }; + + let target_tcp_stream = match ProxyTcpStream::from_std(target_tcp_stream) { + Ok(target_tcp_stream) => target_tcp_stream, + Err(error) => { + warn!("error setting up target tcp stream: {}", error); + return Ok(()); + } + }; + + let child = ClientChild::new(tls_hostname, tls_ca_certs, maybe_identity, source_pipe_stream, target_tcp_stream)?; + child.run() +} + +fn run_child(arguments: &clap::ArgMatches<'static>, + log_level: log::Level) + -> Result<(), failure::Error> +{ + let logger = child::logger::Logger { level: log_level }; + log::set_boxed_logger(Box::new(logger)).expect("logger already set"); + + let source_fd = arguments.value_of("source_fd").expect("required argument").parse::().context("invalid --source-fd")?; + + child::seccomp::close_all_fds(&[ + libc::STDIN_FILENO, + libc::STDOUT_FILENO, + libc::STDERR_FILENO, + source_fd, + ].iter().copied().collect()).context("error closing all fds")?; + + let ca_cert_path = Path::new(arguments.value_of("ca_file").expect("required argument")); + let ca_cert_pem = fs::read_to_string(ca_cert_path).with_context(|_| format_err!("error reading ca certificate file {}", ca_cert_path.display()))?; + + let ca_cert = child::tls::CaCertificate::from_pem(ca_cert_pem.as_bytes()).context("invalid ca certificate")?; + let key_path = Path::new(arguments.value_of("key_file").expect("required argument")); + let identity = Identity::from_pkcs12_file(&key_path, "").context("invalid server certificate")?; + let source_tcp_stream = match ProxyTcpStream::from_std(unsafe { TcpStream::from_raw_fd(source_fd) }) { + Ok(source_tcp_stream) => source_tcp_stream, + Err(error) => { + warn!("error setting up source tcp stream: {}", error); + return Ok(()); + } + }; + let target_pipe_stream = match ProxyPipeStream::stdio() { + Ok(target_pipe_stream) => target_pipe_stream, + Err(error) => { + warn!("error setting up target pipe stream: {}", error); + return Ok(()); + } + }; + + let child = ServerChild::new(ca_cert, identity, source_tcp_stream, target_pipe_stream)?; + child.run() +} + +fn immediate_exit(exit_code: i32) -> ! { + unsafe { libc::_exit(exit_code) } +} + +fn read_config(config_file_path: &Path) -> Result { + let config_file = fs::File::open(config_file_path) + .with_context(|_| format_err!("error opening config file {}", config_file_path.display()))?; + let config = Config::from_reader(config_file) + .with_context(|_| format_err!("error reading config file {}", config_file_path.display()))?; + Ok(config) +} + +fn parse_arguments() -> clap::ArgMatches<'static> { + // + // common + // + + let debug_argument = + clap::Arg::with_name("debug") + .long("debug") + .help("emit debug logging"); + + let config_file_argument = + clap::Arg::with_name("config_file") + .takes_value(true) + .long("config-file") + .value_name("config_file_path") + .help("Path to YAML config file"); + // + // client subcommand + // + + let client_config_file_argument = + config_file_argument + .clone() + .required_unless_all(&[ + "ca_group", + "target_hostname_group", + ]); + + let target_fd_argument = + clap::Arg::with_name("target_fd") + .takes_value(true) + .long("target-fd") + .value_name("target_fd") + .help("File descriptor number corresponding to the target connection to proxy to"); + + let child_target_address_argument = + clap::Arg::with_name("target_address") + .takes_value(true) + .long("target-address") + .value_name("target_address") + .help("ip:port address of target to connect to"); + + let target_group = + clap::ArgGroup::with_name("target_group") + .arg("target_fd") + .arg("target_address") + .required(true); + + let allow_invalid_target_hostname_argument = + clap::Arg::with_name("allow_invalid_target_hostname") + .long("allow-invalid-target-hostname") + .help("Allow any hostname during server certificate validation"); + + let target_hostname_argument = + clap::Arg::with_name("target_hostname") + .takes_value(true) + .long("target-hostname") + .value_name("target_hostname") + .help("Hostname of target to use during server certificate validation"); + + let target_hostname_group = + clap::ArgGroup::with_name("target_hostname_group") + .arg("allow_invalid_target_hostname") + .arg("target_hostname"); + + let ca_file_argument = + clap::Arg::with_name("ca_file") + .takes_value(true) + .multiple(true) + .long("ca-file") + .value_name("ca_file_path") + .help("Path to PEM-encoded ca certificate to use during certificate validation"); + + let ca_system = + clap::Arg::with_name("ca_system") + .long("ca-system") + .help("Use system certificates in /etc/ssl/certs/ during certificate validation"); + + let ca_group = + clap::ArgGroup::with_name("ca_group") + .multiple(true) + .arg("ca_file") + .arg("ca_system"); + + let client_key_file_argument = + clap::Arg::with_name("key_file") + .takes_value(true) + .long("key-file") + .value_name("key_file_path") + .help("Path to DER-encoded PKCS12 client key and certificate"); + + let client_subcommand = + clap::SubCommand::with_name("client") + .arg(client_config_file_argument) + .arg(target_fd_argument) + .arg(child_target_address_argument) + .group(target_group) + .arg(allow_invalid_target_hostname_argument) + .arg(target_hostname_argument) + .group(target_hostname_group) + .arg(ca_system) + .arg(ca_file_argument.clone()) + .group(ca_group) + .arg(client_key_file_argument) + .about("start as a proxy client (e.g. TLS-initiating forward proxy)"); + + // + // child subcommand + // + + let source_fd_argument = + clap::Arg::with_name("source_fd") + .takes_value(true) + .required(true) + .long("source-fd") + .value_name("source_fd") + .help("File descriptor number corresponding to the source connection to proxy"); + + let server_key_file_argument = + clap::Arg::with_name("key_file") + .takes_value(true) + .required(true) + .long("key-file") + .value_name("key_file_path") + .help("Path to DER-encoded PKCS12 server key and certificate"); + + let child_subcommand = + clap::SubCommand::with_name("child") + .setting(clap::AppSettings::Hidden) + .arg(ca_file_argument) + .arg(server_key_file_argument) + .arg(source_fd_argument) + .about("start as proxy server child process"); + + clap::App::new(clap::crate_name!()) + .version(clap::crate_version!()) + .about(clap::crate_description!()) + .author(clap::crate_authors!()) + .setting(clap::AppSettings::VersionlessSubcommands) + .setting(clap::AppSettings::SubcommandRequiredElseHelp) + .subcommand(client_subcommand) + .subcommand(child_subcommand) + .arg(debug_argument) + .get_matches() +} diff --git a/service/kbuptlsd/src/proxy_child.rs b/service/kbuptlsd/src/proxy_child.rs new file mode 100644 index 0000000..916771f --- /dev/null +++ b/service/kbuptlsd/src/proxy_child.rs @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::io; +use std::io::prelude::*; +use std::net::{SocketAddr}; +use std::os::unix::prelude::*; +use std::process::{Child, ChildStdin, ChildStdout, ChildStderr}; + +use futures::prelude::*; +use log::{info, debug}; +use mio::unix::{EventedFd}; +use tokio::codec::{FramedRead, LinesCodec}; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::reactor::{PollEvented2}; + +use crate::util; + +pub struct TlsProxyChild { + child: Child, + address: SocketAddr, +} + +pub struct TlsProxyStream { + stdin: TlsProxyStdioAsync, + stdout: TlsProxyStdioAsync, + address: SocketAddr, +} + +pub type TlsProxyStderrStream = FramedRead, LinesCodec>; + +pub struct TlsProxyStdioAsync { + stdio: Option>>, +} + +struct TlsProxyStdioEvented { + stdio: T, +} + +// +// TlsProxyChild impls +// + +impl TlsProxyChild { + pub(crate) fn new(child: Child, address: SocketAddr) -> Self { + Self { child, address } + } + + pub fn peer_addr(&self) -> SocketAddr { + self.address + } + + pub fn pid(&self) -> u32 { + self.child.id() + } + + pub fn into_streams(self) -> Result<(TlsProxyStream, TlsProxyStderrStream), io::Error> { + let child_stdin = self.child.stdin.ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe, "stdin"))?; + let child_stdout = self.child.stdout.ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe, "stdout"))?; + let child_stderr = self.child.stderr.ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe, "stderr"))?; + let stdio_stream = TlsProxyStream { + stdin: TlsProxyStdioAsync::new(child_stdin)?, + stdout: TlsProxyStdioAsync::new(child_stdout)?, + address: self.address, + }; + let child_stderr = TlsProxyStdioAsync::new(child_stderr)?; + let stderr_stream = FramedRead::new(child_stderr, LinesCodec::new()); + Ok((stdio_stream, stderr_stream)) + } +} + +// +// TlsProxyStream impls +// + +impl TlsProxyStream { + pub fn peer_addr(&self) -> SocketAddr { + self.address + } + + pub fn proxy_to(self, target_address: SocketAddr) -> impl Future { + let address = self.address; + + let target_tcp_stream = tokio::net::TcpStream::connect(&target_address).map_err(move |error: io::Error| { + info!("{} => error connecting to target {}: {}", address, target_address, error); + }); + + let proxied = target_tcp_stream.map(move |target_tcp_stream: tokio::net::TcpStream| { + info!("{} => connection established to target {}", address, target_address); + + let (target_tcp_stream_rx, target_tcp_stream_tx) = target_tcp_stream.split(); + + let proxied_stdin = tokio::io::copy(target_tcp_stream_rx, self.stdin).then(move |result: io::Result<_>| { + match result { + Ok(_) => info!("{} => connection closed by target", address), + Err(error) => info!("{} => error proxying target -> source: {}", address, error), + } + Ok(()) + }); + + let proxied_stdout = tokio::io::copy(self.stdout, target_tcp_stream_tx).map_err(move |error: io::Error| { + info!("{} => error proxying source -> target: {}", address, error); + }); + let proxied_stdout = proxied_stdout.and_then(move |(_, _, target_tcp_stream_tx)| { + info!("{} => connection closed by source", address); + + tokio::io::shutdown(target_tcp_stream_tx).map_err(move |error: io::Error| { + info!("{} => error shutting down target: {}", address, error); + }) + }); + let proxied_stdout = proxied_stdout.map(move |_| { + debug!("{} => shut down target", address); + }); + + tokio::spawn(proxied_stdin); + tokio::spawn(proxied_stdout); + }); + + proxied + } +} + +impl Read for TlsProxyStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.stdout.read(buf) + } + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + self.stdout.read_vectored(bufs) + } +} + +impl Write for TlsProxyStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.stdin.write(buf) + } + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.stdin.write_vectored(bufs) + } + fn flush(&mut self) -> io::Result<()> { + self.stdin.flush() + } +} + +impl AsyncRead for TlsProxyStream { +} + +impl AsyncWrite for TlsProxyStream { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.stdin.shutdown() + } +} + +// +// TlsProxyStdioAsync impls +// + +impl TlsProxyStdioAsync { + fn new(stdio: T) -> Result { + util::set_nonblocking(stdio.as_raw_fd())?; + Ok(Self { + stdio: Some(PollEvented2::new(TlsProxyStdioEvented { stdio })), + }) + } +} + +impl Read for TlsProxyStdioAsync { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match &mut self.stdio { + Some(stdio) => stdio.read(buf), + None => Err(io::ErrorKind::NotConnected.into()), + } + } + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + match &mut self.stdio { + Some(stdio) => stdio.read_vectored(bufs), + None => Err(io::ErrorKind::NotConnected.into()), + } + } +} + +impl Write for TlsProxyStdioAsync { + fn write(&mut self, buf: &[u8]) -> io::Result { + match &mut self.stdio { + Some(stdio) => stdio.write(buf), + None => Err(io::ErrorKind::NotConnected.into()), + } + } + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + match &mut self.stdio { + Some(stdio) => stdio.write_vectored(bufs), + None => Err(io::ErrorKind::NotConnected.into()), + } + } + fn flush(&mut self) -> io::Result<()> { + match &mut self.stdio { + Some(stdio) => stdio.flush(), + None => Err(io::ErrorKind::NotConnected.into()), + } + } +} + +impl AsyncRead for TlsProxyStdioAsync { +} + +impl AsyncWrite for TlsProxyStdioAsync { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.stdio = None; + Ok(Async::Ready(())) + } +} + +// +// TlsProxyStdioEvented impls +// + +impl Read for TlsProxyStdioEvented { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.stdio.read(buf) + } + fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result { + self.stdio.read_vectored(bufs) + } +} + +impl Write for TlsProxyStdioEvented { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.stdio.write(buf) + } + fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result { + self.stdio.write_vectored(bufs) + } + fn flush(&mut self) -> io::Result<()> { + self.stdio.flush() + } +} + +impl mio::Evented for TlsProxyStdioEvented { + fn register(&self, poll: &mio::Poll, token: mio::Token, interest: mio::Ready, opts: mio::PollOpt) + -> io::Result<()> + { + EventedFd(&self.stdio.as_raw_fd()).register(poll, token, interest, opts) + } + + fn reregister(&self, poll: &mio::Poll, token: mio::Token, interest: mio::Ready, opts: mio::PollOpt) + -> io::Result<()> + { + EventedFd(&self.stdio.as_raw_fd()).reregister(poll, token, interest, opts) + } + + fn deregister(&self, poll: &mio::Poll) -> io::Result<()> { + EventedFd(&self.stdio.as_raw_fd()).deregister(poll) + } +} diff --git a/service/kbuptlsd/src/server.rs b/service/kbuptlsd/src/server.rs new file mode 100644 index 0000000..c015894 --- /dev/null +++ b/service/kbuptlsd/src/server.rs @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod logger; + +use std::io; +use std::net::{SocketAddr, ToSocketAddrs}; +use std::os::unix::prelude::*; +use std::path::{Path, PathBuf}; +use std::process::{Child, Command}; +use std::time::{Duration}; + +use failure::{ResultExt}; +use futures::prelude::*; +use futures::stream; +use futures::{try_ready}; +use log::{error, warn, info, debug, log}; +use tk_listen::{ListenExt}; +use tokio::net::{TcpListener}; + +use crate::child; +use crate::counter::*; +use crate::proxy_child::*; +use crate::util; +use crate::util::{CommandExt}; + +const LISTEN_RETRY_DELAY: Duration = Duration::from_secs(5); + +pub struct TlsProxyListener { + bin_path: PathBuf, + arguments: TlsProxyListenerArguments, + max_connections: usize, + tcp_listener: TcpListener, +} + +pub enum TlsProxyListenerArguments { + Config { + config_file: PathBuf, + key_file: PathBuf, + }, + NoConfig { + ca_file: PathBuf, + key_file: PathBuf, + }, +} + +// +// TlsProxyListener impls +// + +impl TlsProxyListener { + pub fn new(listen_host_port: impl ToSocketAddrs, + bin_path: PathBuf, + max_connections: usize, + arguments: TlsProxyListenerArguments) + -> Result + { + bin_path.metadata().with_context(|_| format!("error opening bin file {}", bin_path.display()))?; + + match &arguments { + TlsProxyListenerArguments::Config { config_file, key_file } => { + config_file.metadata().with_context(|_| format!("error opening config file {}", config_file.display()))?; + key_file.metadata().with_context(|_| format!("error opening key file {}", key_file.display()))?; + } + TlsProxyListenerArguments::NoConfig { ca_file, key_file } => { + ca_file.metadata().with_context(|_| format!("error opening ca certificate file {}", ca_file.display()))?; + key_file.metadata().with_context(|_| format!("error opening key file {}", key_file.display()))?; + } + } + + let listen_addr = util::to_socket_addr(listen_host_port).context("invalid listen address")?; + let tcp_listener = TcpListener::bind(&listen_addr).with_context(|_| format!("error listening on {}", listen_addr))?; + + Ok(Self { bin_path, arguments, max_connections, tcp_listener }) + } + + pub fn listen_addr(&self) -> Result { + self.tcp_listener.local_addr() + } + + pub fn proxy_to(self, target_address: SocketAddr) -> impl Future { + self.into_stream().for_each(move |proxy_child: TlsProxyStream| { + proxy_child.proxy_to(target_address).or_else(|()| Ok(())) + }) + } + pub fn into_stream(self) -> impl Stream { + let Self { bin_path, arguments, max_connections, mut tcp_listener } = self; + + let connection_counter = AtomicCounter::default(); + + let tcp_connections = stream::poll_fn(move || Ok(Async::Ready(Some(try_ready!(tcp_listener.poll_accept_std()))))); + let tcp_connections = tcp_connections.sleep_on_error(LISTEN_RETRY_DELAY).map_err(|()| { + failure::format_err!("listen error") + }); + let proxy_children = tcp_connections.filter_map(move |(source_stream, source_address): (std::net::TcpStream, SocketAddr)| { + let connection_count = connection_counter.count(); + if connection_count >= max_connections { + warn!("{} => connection dropped due to max connections {}", source_address, connection_count); + return None; + } + let counter_guard = connection_counter.inc(); + + match Self::spawn_proxy(&bin_path, &arguments, source_stream) { + Ok(child) => { + let child = TlsProxyChild::new(child, source_address); + let child_pid = child.pid(); + + let (stdio_stream, stderr_stream) = match child.into_streams() { + Ok(child_streams) => child_streams, + Err(error) => { + info!("{} => error setting up child proxy streams: {}", source_address, error); + return None; + } + }; + + let stderr_logger = Self::log_proxy_stderr(stderr_stream, source_address, child_pid).then(move |_| { + drop(counter_guard); + Ok(()) + }); + tokio::spawn(stderr_logger); + + Some(stdio_stream.into()) + } + Err(error) => { + error!("{} => error spawning child proxy: {}", source_address, error); + None + } + } + }); + + proxy_children + } + + fn log_proxy_stderr(stderr_stream: TlsProxyStderrStream, + source_address: SocketAddr, + child_pid: u32) + -> impl Future + { + let log_target = format!("kbuptlsd-{}", child_pid); + let logger = stderr_stream.for_each(move |line: String| { + let (log_level, line) = child::logger::parse_line(&line); + log!(target: &log_target, log_level, "{} => {}", source_address, line); + Ok(()) + }); + logger.then(move |result: Result<(), io::Error>| { + match result { + Ok(()) => { + debug!("{} => child process died", source_address); + Ok(()) + } + Err(error) => { + warn!("{} => error reading from child stderr: {}", source_address, error); + Err(()) + } + } + }) + } + + fn spawn_proxy(bin_path: &Path, + arguments: &TlsProxyListenerArguments, + source_stream: std::net::TcpStream) + -> Result + { + let mut child = Command::new(bin_path); + child.stdin(std::process::Stdio::piped()); + child.stdout(std::process::Stdio::piped()); + child.stderr(std::process::Stdio::piped()); + + child.preserve_fd(&source_stream); + + if log::max_level() >= log::Level::Debug { + child.arg("--debug"); + } + + child.arg("child"); + match &arguments { + TlsProxyListenerArguments::Config { config_file, key_file } => { + child.arg("--config-file").arg(config_file); + child.arg("--key-file").arg(key_file); + } + TlsProxyListenerArguments::NoConfig { ca_file, key_file } => { + child.arg("--ca-file").arg(ca_file); + child.arg("--key-file").arg(key_file); + } + } + child.arg("--source-fd").arg(source_stream.as_raw_fd().to_string()); + + Ok(child.spawn()?) + } +} diff --git a/service/kbuptlsd/src/server/logger.rs b/service/kbuptlsd/src/server/logger.rs new file mode 100644 index 0000000..f5a52af --- /dev/null +++ b/service/kbuptlsd/src/server/logger.rs @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::cell::*; +use std::rc::*; + +use chrono::format::{StrftimeItems}; + +const TIMESTAMP_FORMAT: &str = "%Y-%m-%d %H:%M:%S.%3f"; + +pub struct Logger { + pub level: log::Level, +} + +thread_local! { + static TIMESTAMP_FORMAT_ITEMS: Cell]>>> = Default::default(); +} + +impl log::Log for Logger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= self.level + } + + fn log(&self, record: &log::Record) { + if self.enabled(record.metadata()) { + let syslog_severity = match record.level() { + log::Level::Error => '3', + log::Level::Warn => '4', + log::Level::Info => '6', + log::Level::Debug => '7', + log::Level::Trace => '7', + }; + let log_level_string = match record.level() { + log::Level::Error => "ERRO", + log::Level::Warn => "WARN", + log::Level::Info => "INFO", + log::Level::Debug => "DEBG", + log::Level::Trace => "TRCE", + }; + + let timespec = get_coarse_time(); + let datetime = chrono::NaiveDateTime::from_timestamp(timespec.0, timespec.1); + let timestamp_format = timestamp_format_items(); + let formatted_timestamp = datetime.format_with_items(timestamp_format.iter().cloned()); + + let line = format!( + "<{}>{} {:<4} [{}] === {}\n", + syslog_severity, + formatted_timestamp, + log_level_string, + record.target(), + record.args(), + ); + eprint!("{}", line); + } + } + + fn flush(&self) { + } +} + +fn timestamp_format_items() -> Rc<[chrono::format::Item<'static>]> { + TIMESTAMP_FORMAT_ITEMS.with(|format_items_cell: &Cell<_>| { + let format_items = match format_items_cell.take() { + Some(format_items) => format_items, + None => StrftimeItems::new(TIMESTAMP_FORMAT).collect::>(), + }; + let format_items_2 = Rc::clone(&format_items); + format_items_cell.set(Some(format_items)); + format_items_2 + }) +} + +#[cfg(target_os = "linux")] +fn get_coarse_time() -> (i64, u32) { + let mut timespec = libc::timespec { + tv_sec: Default::default(), + tv_nsec: Default::default(), + }; + let _ignore = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME_COARSE, &mut timespec) }; + (timespec.tv_sec, timespec.tv_nsec as u32) +} +#[cfg(not(target_os = "linux"))] +fn get_coarse_time() -> (i64, u32) { + let timespec = time::get_time(); + (timespec.sec, timespec.nsec as u32) +} diff --git a/service/kbuptlsd/src/util.rs b/service/kbuptlsd/src/util.rs new file mode 100644 index 0000000..6401526 --- /dev/null +++ b/service/kbuptlsd/src/util.rs @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::net::{SocketAddr, ToSocketAddrs}; +use std::io; +use std::os::unix::prelude::*; +use std::os::unix::process::{CommandExt as _}; +use std::process::{Command}; + +use nix::fcntl; +use nix::fcntl::{FdFlag}; + +pub use rustunnel::util::*; + +pub trait CommandExt { + fn preserve_fd(&mut self, fd: &impl AsRawFd); +} + +pub fn to_socket_addr(address: impl ToSocketAddrs) -> io::Result { + address.to_socket_addrs()? + .next() + .ok_or(io::Error::new(io::ErrorKind::Other, "empty address")) +} + +impl CommandExt for Command { + fn preserve_fd(&mut self, fd: &impl AsRawFd) { + let fd = fd.as_raw_fd(); + unsafe { + self.pre_exec(move || { + let fd_flag_bits = convert_nix(fcntl::fcntl(fd, fcntl::F_GETFD))?; + let mut fd_flags = FdFlag::from_bits(fd_flag_bits).unwrap_or_else(FdFlag::empty); + fd_flags.remove(FdFlag::FD_CLOEXEC); + assert_eq!(convert_nix(fcntl::fcntl(fd, fcntl::F_SETFD(fd_flags)))?, 0); + Ok(()) + }); + } + } +} diff --git a/service/kbuptlsd/tests/mock_child.sh b/service/kbuptlsd/tests/mock_child.sh new file mode 100755 index 0000000..f23673b --- /dev/null +++ b/service/kbuptlsd/tests/mock_child.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +echo "INFO mock child started with args: $*" 1>&2 + +while [[ "$1" != "--source-fd" ]]; do + if [[ $# -eq 0 ]]; then + echo "ERRO no --source-fd specified" 1>&2 + exit 1 + fi + shift +done + +shift +fd=$(echo "$1" | sed -nEe "/^[0-9]+$/p") + +if [[ -z "$fd" ]]; then + echo "ERRO bad --source-fd: $1" 1>&2 + exit 1 +fi + +while IFS= read -u "$fd" line; do + printf "received message: %s\n" "$line" 1>&2 + printf "%s\n" "$line" 1>&"${fd}" +done + +echo "INFO mock child exiting" 1>&2 diff --git a/service/kbuptlsd/tests/server_test.rs b/service/kbuptlsd/tests/server_test.rs new file mode 100644 index 0000000..16ef13a --- /dev/null +++ b/service/kbuptlsd/tests/server_test.rs @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#[macro_use] +mod util; + +use std::net::{TcpStream}; +use std::sync::*; +use std::time::{Duration}; +use std::thread; + +use futures::future; +use futures::prelude::*; +use futures::sync::oneshot; +use kbuptlsd::server::*; +use log::{info}; + +use self::util::*; + +fn test_paths() -> TlsProxyListenerArguments { + TlsProxyListenerArguments::NoConfig { + ca_file: r"/dev/null".into(), + key_file: r"/dev/null".into(), + } +} + +#[test] +fn test_connection_limit() { + setup_common(); + + let max_connections = 2; + + let server = TlsProxyListener::new("127.0.0.1:0", r"tests/mock_child.sh".into(), max_connections, test_paths()).expect(error_line!()); + let listen_addr = server.listen_addr().expect(error_line!()); + let (tx, rx) = oneshot::channel(); + thread::spawn(move || { + let mut runtime = tokio::runtime::current_thread::Builder::new().build().expect(error_line!()); + let proxied = server.into_stream().for_each(|_| Ok(())); + match runtime.block_on(rx.select2(proxied)) { + Ok(future::Either::B(((), _))) => panic!("server terminated"), + Err(future::Either::B((error, _))) => panic!("server init error: {}", error), + Ok(future::Either::A(((), _))) | + Err(future::Either::A((_, _))) => (), + } + }); + + let thread_count = max_connections + 10; + let barrier = Arc::new(Barrier::new(thread_count)); + let mut threads = Vec::with_capacity(thread_count); + for connection_idx in 0..thread_count { + let barrier = barrier.clone(); + threads.push(thread::spawn(move || { + // expect connections up to max_connections to succeeed + let mut successful_tcp_stream = if connection_idx < max_connections { + info!(target: "server_test", "starting connection {}", connection_idx); + let tcp_stream = TcpStream::connect(listen_addr).expect("error connecting to test server"); + assert_stream_open(&tcp_stream).expect(error_line!()); + Some(tcp_stream) + } else { + None + }; + barrier.wait(); + + // expect subsequent connections to fail + if connection_idx >= max_connections { + info!(target: "server_test", "starting connection {}", connection_idx); + let tcp_stream = TcpStream::connect(listen_addr).expect("error connecting to test server"); + assert_stream_closed(tcp_stream).expect(error_line!()); + } + barrier.wait(); + + // reopen the connections + if connection_idx < max_connections { + let tcp_stream = successful_tcp_stream.take().expect(error_line!()); + info!(target: "server_test", "closing connection {} at {}", connection_idx, tcp_stream.local_addr().expect(error_line!())); + tcp_stream.shutdown(std::net::Shutdown::Both).expect(error_line!()); + assert_stream_closed(tcp_stream).expect(error_line!()); + + successful_tcp_stream = loop { + info!(target: "server_test", "starting connection {}", connection_idx); + let tcp_stream = TcpStream::connect(listen_addr).expect("error connecting to test server"); + if let Ok(()) = assert_stream_open(&tcp_stream) { + break Some(tcp_stream); + } + thread::sleep(Duration::from_millis(1)) + }; + } + barrier.wait(); + + // clean up + if let Some(tcp_stream) = successful_tcp_stream { + info!(target: "server_test", "closing connection {}", connection_idx); + assert_stream_open(&tcp_stream).expect(error_line!()); + tcp_stream.shutdown(std::net::Shutdown::Both).expect(error_line!()); + assert_stream_closed(tcp_stream).expect(error_line!()); + } + })); + } + + for thread in threads { + thread.join().expect(error_line!()); + } + drop(tx); +} + +// +// helpers +// + +fn setup_common() { + setup_logger(); +} + +fn setup_logger() { + let logger = logger::Logger { level: log::Level::Debug }; + log::set_boxed_logger(Box::new(logger)).expect("logger already set"); + log::set_max_level(log::Level::Debug.to_level_filter()); +} diff --git a/service/kbuptlsd/tests/util.rs b/service/kbuptlsd/tests/util.rs new file mode 100644 index 0000000..cd62c48 --- /dev/null +++ b/service/kbuptlsd/tests/util.rs @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(dead_code, unused_macros)] + +use std::cell::*; +use std::io::prelude::*; + +use failure::{ResultExt}; +use rand_chacha::{ChaChaRng}; +use rand_core::{RngCore, SeedableRng}; + +thread_local! { + pub static RAND: RefCell = RefCell::new(SeedableRng::seed_from_u64(0)); +} + +macro_rules! error_line { + () => { concat!(module_path!(), ":", line!()) }; +} + +pub fn assert_stream_closed(mut tcp_stream: impl Read) -> Result<(), failure::Error> { + let mut buf = [0; 128]; + match tcp_stream.read(&mut buf[..]).context("error reading from socket")? { + 0 => Ok(()), + n => Err(failure::format_err!("socket not closed, read {} bytes: {}", n, String::from_utf8_lossy(&buf[..]))), + } +} + +pub fn assert_stream_open(mut tcp_stream: impl Read + Write) -> Result<(), failure::Error> { + let rand_id = RAND.with(|rand| rand.borrow_mut().next_u64()); + let message = format!("{:016x} it works!\n", rand_id).into_bytes(); + let mut buffer = vec![0; message.len()]; + + tcp_stream.write_all(&message).context("error writing to socket")?; + let () = tcp_stream.read_exact(&mut buffer).context("error reading from socket")?; + if buffer == message { + Ok(()) + } else { + Err(failure::format_err!("mock client output doesnt match: {} != {}", String::from_utf8_lossy(&buffer), String::from_utf8_lossy(&message))) + } +} diff --git a/service/rustunnel/.cargo/config b/service/rustunnel/.cargo/config new file mode 100644 index 0000000..fe9c8d6 --- /dev/null +++ b/service/rustunnel/.cargo/config @@ -0,0 +1,2 @@ +[build] +target-dir = "../build/target" diff --git a/service/rustunnel/Cargo.toml b/service/rustunnel/Cargo.toml new file mode 100644 index 0000000..33dc2b3 --- /dev/null +++ b/service/rustunnel/Cargo.toml @@ -0,0 +1,33 @@ +[package] +authors = ["Open Whisper Systems"] +name = "rustunnel" +version = "0.1.0" +license = "AGPL-3.0-or-later" +description = "Sandboxed TLS tunnel library" +edition = "2018" +build = "build.rs" + +[dependencies] +clear_on_drop = "0.2" +failure = "0.1" +libc = "0.2" +log = { version = "0.4", features = ["std"] } +nix = "0.13" +openssl = { rev = "ea6761d5e7b63b415b7fda6ba5c5026f4218bcd0", git = "https://github.com/geogriff-signal/rust-openssl.git" } +seccomp-sys = "0.1" + +[build-dependencies] +cc = "1.0" + +[dev-dependencies] +native-tls = "0.2" +rand_core = "0.5" +rand_chacha = "0.2" + +[[test]] +name = "client_child_test" +harness = false + +[[test]] +name = "server_child_test" +harness = false diff --git a/service/rustunnel/build.rs b/service/rustunnel/build.rs new file mode 100644 index 0000000..045fb3c --- /dev/null +++ b/service/rustunnel/build.rs @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +fn main() { + let mut cc = cc::Build::new(); + cc.file("c_src/malloc.c") + .flag("-fno-builtin") + .include("c_src") + .compile("malloc"); +} diff --git a/service/rustunnel/c_src/dlmalloc/malloc-2.8.6.c b/service/rustunnel/c_src/dlmalloc/malloc-2.8.6.c new file mode 100644 index 0000000..649cfbc --- /dev/null +++ b/service/rustunnel/c_src/dlmalloc/malloc-2.8.6.c @@ -0,0 +1,6280 @@ +/* + This is a version (aka dlmalloc) of malloc/free/realloc written by + Doug Lea and released to the public domain, as explained at + http://creativecommons.org/publicdomain/zero/1.0/ Send questions, + comments, complaints, performance data, etc to dl@cs.oswego.edu + +* Version 2.8.6 Wed Aug 29 06:57:58 2012 Doug Lea + Note: There may be an updated version of this malloc obtainable at + ftp://gee.cs.oswego.edu/pub/misc/malloc.c + Check before installing! + +* Quickstart + + This library is all in one file to simplify the most common usage: + ftp it, compile it (-O3), and link it into another program. All of + the compile-time options default to reasonable values for use on + most platforms. You might later want to step through various + compile-time and dynamic tuning options. + + For convenience, an include file for code using this malloc is at: + ftp://gee.cs.oswego.edu/pub/misc/malloc-2.8.6.h + You don't really need this .h file unless you call functions not + defined in your system include files. The .h file contains only the + excerpts from this file needed for using this malloc on ANSI C/C++ + systems, so long as you haven't changed compile-time options about + naming and tuning parameters. If you do, then you can create your + own malloc.h that does include all settings by cutting at the point + indicated below. Note that you may already by default be using a C + library containing a malloc that is based on some version of this + malloc (for example in linux). You might still want to use the one + in this file to customize settings or to avoid overheads associated + with library versions. + +* Vital statistics: + + Supported pointer/size_t representation: 4 or 8 bytes + size_t MUST be an unsigned type of the same width as + pointers. (If you are using an ancient system that declares + size_t as a signed type, or need it to be a different width + than pointers, you can use a previous release of this malloc + (e.g. 2.7.2) supporting these.) + + Alignment: 8 bytes (minimum) + This suffices for nearly all current machines and C compilers. + However, you can define MALLOC_ALIGNMENT to be wider than this + if necessary (up to 128bytes), at the expense of using more space. + + Minimum overhead per allocated chunk: 4 or 8 bytes (if 4byte sizes) + 8 or 16 bytes (if 8byte sizes) + Each malloced chunk has a hidden word of overhead holding size + and status information, and additional cross-check word + if FOOTERS is defined. + + Minimum allocated size: 4-byte ptrs: 16 bytes (including overhead) + 8-byte ptrs: 32 bytes (including overhead) + + Even a request for zero bytes (i.e., malloc(0)) returns a + pointer to something of the minimum allocatable size. + The maximum overhead wastage (i.e., number of extra bytes + allocated than were requested in malloc) is less than or equal + to the minimum size, except for requests >= mmap_threshold that + are serviced via mmap(), where the worst case wastage is about + 32 bytes plus the remainder from a system page (the minimal + mmap unit); typically 4096 or 8192 bytes. + + Security: static-safe; optionally more or less + The "security" of malloc refers to the ability of malicious + code to accentuate the effects of errors (for example, freeing + space that is not currently malloc'ed or overwriting past the + ends of chunks) in code that calls malloc. This malloc + guarantees not to modify any memory locations below the base of + heap, i.e., static variables, even in the presence of usage + errors. The routines additionally detect most improper frees + and reallocs. All this holds as long as the static bookkeeping + for malloc itself is not corrupted by some other means. This + is only one aspect of security -- these checks do not, and + cannot, detect all possible programming errors. + + If FOOTERS is defined nonzero, then each allocated chunk + carries an additional check word to verify that it was malloced + from its space. These check words are the same within each + execution of a program using malloc, but differ across + executions, so externally crafted fake chunks cannot be + freed. This improves security by rejecting frees/reallocs that + could corrupt heap memory, in addition to the checks preventing + writes to statics that are always on. This may further improve + security at the expense of time and space overhead. (Note that + FOOTERS may also be worth using with MSPACES.) + + By default detected errors cause the program to abort (calling + "abort()"). You can override this to instead proceed past + errors by defining PROCEED_ON_ERROR. In this case, a bad free + has no effect, and a malloc that encounters a bad address + caused by user overwrites will ignore the bad address by + dropping pointers and indices to all known memory. This may + be appropriate for programs that should continue if at all + possible in the face of programming errors, although they may + run out of memory because dropped memory is never reclaimed. + + If you don't like either of these options, you can define + CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything + else. And if if you are sure that your program using malloc has + no errors or vulnerabilities, you can define INSECURE to 1, + which might (or might not) provide a small performance improvement. + + It is also possible to limit the maximum total allocatable + space, using malloc_set_footprint_limit. This is not + designed as a security feature in itself (calls to set limits + are not screened or privileged), but may be useful as one + aspect of a secure implementation. + + Thread-safety: NOT thread-safe unless USE_LOCKS defined non-zero + When USE_LOCKS is defined, each public call to malloc, free, + etc is surrounded with a lock. By default, this uses a plain + pthread mutex, win32 critical section, or a spin-lock if if + available for the platform and not disabled by setting + USE_SPIN_LOCKS=0. However, if USE_RECURSIVE_LOCKS is defined, + recursive versions are used instead (which are not required for + base functionality but may be needed in layered extensions). + Using a global lock is not especially fast, and can be a major + bottleneck. It is designed only to provide minimal protection + in concurrent environments, and to provide a basis for + extensions. If you are using malloc in a concurrent program, + consider instead using nedmalloc + (http://www.nedprod.com/programs/portable/nedmalloc/) or + ptmalloc (See http://www.malloc.de), which are derived from + versions of this malloc. + + System requirements: Any combination of MORECORE and/or MMAP/MUNMAP + This malloc can use unix sbrk or any emulation (invoked using + the CALL_MORECORE macro) and/or mmap/munmap or any emulation + (invoked using CALL_MMAP/CALL_MUNMAP) to get and release system + memory. On most unix systems, it tends to work best if both + MORECORE and MMAP are enabled. On Win32, it uses emulations + based on VirtualAlloc. It also uses common C library functions + like memset. + + Compliance: I believe it is compliant with the Single Unix Specification + (See http://www.unix.org). Also SVID/XPG, ANSI C, and probably + others as well. + +* Overview of algorithms + + This is not the fastest, most space-conserving, most portable, or + most tunable malloc ever written. However it is among the fastest + while also being among the most space-conserving, portable and + tunable. Consistent balance across these factors results in a good + general-purpose allocator for malloc-intensive programs. + + In most ways, this malloc is a best-fit allocator. Generally, it + chooses the best-fitting existing chunk for a request, with ties + broken in approximately least-recently-used order. (This strategy + normally maintains low fragmentation.) However, for requests less + than 256bytes, it deviates from best-fit when there is not an + exactly fitting available chunk by preferring to use space adjacent + to that used for the previous small request, as well as by breaking + ties in approximately most-recently-used order. (These enhance + locality of series of small allocations.) And for very large requests + (>= 256Kb by default), it relies on system memory mapping + facilities, if supported. (This helps avoid carrying around and + possibly fragmenting memory used only for large chunks.) + + All operations (except malloc_stats and mallinfo) have execution + times that are bounded by a constant factor of the number of bits in + a size_t, not counting any clearing in calloc or copying in realloc, + or actions surrounding MORECORE and MMAP that have times + proportional to the number of non-contiguous regions returned by + system allocation routines, which is often just 1. In real-time + applications, you can optionally suppress segment traversals using + NO_SEGMENT_TRAVERSAL, which assures bounded execution even when + system allocators return non-contiguous spaces, at the typical + expense of carrying around more memory and increased fragmentation. + + The implementation is not very modular and seriously overuses + macros. Perhaps someday all C compilers will do as good a job + inlining modular code as can now be done by brute-force expansion, + but now, enough of them seem not to. + + Some compilers issue a lot of warnings about code that is + dead/unreachable only on some platforms, and also about intentional + uses of negation on unsigned types. All known cases of each can be + ignored. + + For a longer but out of date high-level description, see + http://gee.cs.oswego.edu/dl/html/malloc.html + +* MSPACES + If MSPACES is defined, then in addition to malloc, free, etc., + this file also defines mspace_malloc, mspace_free, etc. These + are versions of malloc routines that take an "mspace" argument + obtained using create_mspace, to control all internal bookkeeping. + If ONLY_MSPACES is defined, only these versions are compiled. + So if you would like to use this allocator for only some allocations, + and your system malloc for others, you can compile with + ONLY_MSPACES and then do something like... + static mspace mymspace = create_mspace(0,0); // for example + #define mymalloc(bytes) mspace_malloc(mymspace, bytes) + + (Note: If you only need one instance of an mspace, you can instead + use "USE_DL_PREFIX" to relabel the global malloc.) + + You can similarly create thread-local allocators by storing + mspaces as thread-locals. For example: + static __thread mspace tlms = 0; + void* tlmalloc(size_t bytes) { + if (tlms == 0) tlms = create_mspace(0, 0); + return mspace_malloc(tlms, bytes); + } + void tlfree(void* mem) { mspace_free(tlms, mem); } + + Unless FOOTERS is defined, each mspace is completely independent. + You cannot allocate from one and free to another (although + conformance is only weakly checked, so usage errors are not always + caught). If FOOTERS is defined, then each chunk carries around a tag + indicating its originating mspace, and frees are directed to their + originating spaces. Normally, this requires use of locks. + + ------------------------- Compile-time options --------------------------- + +Be careful in setting #define values for numerical constants of type +size_t. On some systems, literal values are not automatically extended +to size_t precision unless they are explicitly casted. You can also +use the symbolic values MAX_SIZE_T, SIZE_T_ONE, etc below. + +WIN32 default: defined if _WIN32 defined + Defining WIN32 sets up defaults for MS environment and compilers. + Otherwise defaults are for unix. Beware that there seem to be some + cases where this malloc might not be a pure drop-in replacement for + Win32 malloc: Random-looking failures from Win32 GDI API's (eg; + SetDIBits()) may be due to bugs in some video driver implementations + when pixel buffers are malloc()ed, and the region spans more than + one VirtualAlloc()ed region. Because dlmalloc uses a small (64Kb) + default granularity, pixel buffers may straddle virtual allocation + regions more often than when using the Microsoft allocator. You can + avoid this by using VirtualAlloc() and VirtualFree() for all pixel + buffers rather than using malloc(). If this is not possible, + recompile this malloc with a larger DEFAULT_GRANULARITY. Note: + in cases where MSC and gcc (cygwin) are known to differ on WIN32, + conditions use _MSC_VER to distinguish them. + +DLMALLOC_EXPORT default: extern + Defines how public APIs are declared. If you want to export via a + Windows DLL, you might define this as + #define DLMALLOC_EXPORT extern __declspec(dllexport) + If you want a POSIX ELF shared object, you might use + #define DLMALLOC_EXPORT extern __attribute__((visibility("default"))) + +MALLOC_ALIGNMENT default: (size_t)(2 * sizeof(void *)) + Controls the minimum alignment for malloc'ed chunks. It must be a + power of two and at least 8, even on machines for which smaller + alignments would suffice. It may be defined as larger than this + though. Note however that code and data structures are optimized for + the case of 8-byte alignment. + +MSPACES default: 0 (false) + If true, compile in support for independent allocation spaces. + This is only supported if HAVE_MMAP is true. + +ONLY_MSPACES default: 0 (false) + If true, only compile in mspace versions, not regular versions. + +USE_LOCKS default: 0 (false) + Causes each call to each public routine to be surrounded with + pthread or WIN32 mutex lock/unlock. (If set true, this can be + overridden on a per-mspace basis for mspace versions.) If set to a + non-zero value other than 1, locks are used, but their + implementation is left out, so lock functions must be supplied manually, + as described below. + +USE_SPIN_LOCKS default: 1 iff USE_LOCKS and spin locks available + If true, uses custom spin locks for locking. This is currently + supported only gcc >= 4.1, older gccs on x86 platforms, and recent + MS compilers. Otherwise, posix locks or win32 critical sections are + used. + +USE_RECURSIVE_LOCKS default: not defined + If defined nonzero, uses recursive (aka reentrant) locks, otherwise + uses plain mutexes. This is not required for malloc proper, but may + be needed for layered allocators such as nedmalloc. + +LOCK_AT_FORK default: not defined + If defined nonzero, performs pthread_atfork upon initialization + to initialize child lock while holding parent lock. The implementation + assumes that pthread locks (not custom locks) are being used. In other + cases, you may need to customize the implementation. + +FOOTERS default: 0 + If true, provide extra checking and dispatching by placing + information in the footers of allocated chunks. This adds + space and time overhead. + +INSECURE default: 0 + If true, omit checks for usage errors and heap space overwrites. + +USE_DL_PREFIX default: NOT defined + Causes compiler to prefix all public routines with the string 'dl'. + This can be useful when you only want to use this malloc in one part + of a program, using your regular system malloc elsewhere. + +MALLOC_INSPECT_ALL default: NOT defined + If defined, compiles malloc_inspect_all and mspace_inspect_all, that + perform traversal of all heap space. Unless access to these + functions is otherwise restricted, you probably do not want to + include them in secure implementations. + +ABORT default: defined as abort() + Defines how to abort on failed checks. On most systems, a failed + check cannot die with an "assert" or even print an informative + message, because the underlying print routines in turn call malloc, + which will fail again. Generally, the best policy is to simply call + abort(). It's not very useful to do more than this because many + errors due to overwriting will show up as address faults (null, odd + addresses etc) rather than malloc-triggered checks, so will also + abort. Also, most compilers know that abort() does not return, so + can better optimize code conditionally calling it. + +PROCEED_ON_ERROR default: defined as 0 (false) + Controls whether detected bad addresses cause them to bypassed + rather than aborting. If set, detected bad arguments to free and + realloc are ignored. And all bookkeeping information is zeroed out + upon a detected overwrite of freed heap space, thus losing the + ability to ever return it from malloc again, but enabling the + application to proceed. If PROCEED_ON_ERROR is defined, the + static variable malloc_corruption_error_count is compiled in + and can be examined to see if errors have occurred. This option + generates slower code than the default abort policy. + +DEBUG default: NOT defined + The DEBUG setting is mainly intended for people trying to modify + this code or diagnose problems when porting to new platforms. + However, it may also be able to better isolate user errors than just + using runtime checks. The assertions in the check routines spell + out in more detail the assumptions and invariants underlying the + algorithms. The checking is fairly extensive, and will slow down + execution noticeably. Calling malloc_stats or mallinfo with DEBUG + set will attempt to check every non-mmapped allocated and free chunk + in the course of computing the summaries. + +ABORT_ON_ASSERT_FAILURE default: defined as 1 (true) + Debugging assertion failures can be nearly impossible if your + version of the assert macro causes malloc to be called, which will + lead to a cascade of further failures, blowing the runtime stack. + ABORT_ON_ASSERT_FAILURE cause assertions failures to call abort(), + which will usually make debugging easier. + +MALLOC_FAILURE_ACTION default: sets errno to ENOMEM, or no-op on win32 + The action to take before "return 0" when malloc fails to be able to + return memory because there is none available. + +HAVE_MORECORE default: 1 (true) unless win32 or ONLY_MSPACES + True if this system supports sbrk or an emulation of it. + +MORECORE default: sbrk + The name of the sbrk-style system routine to call to obtain more + memory. See below for guidance on writing custom MORECORE + functions. The type of the argument to sbrk/MORECORE varies across + systems. It cannot be size_t, because it supports negative + arguments, so it is normally the signed type of the same width as + size_t (sometimes declared as "intptr_t"). It doesn't much matter + though. Internally, we only call it with arguments less than half + the max value of a size_t, which should work across all reasonable + possibilities, although sometimes generating compiler warnings. + +MORECORE_CONTIGUOUS default: 1 (true) if HAVE_MORECORE + If true, take advantage of fact that consecutive calls to MORECORE + with positive arguments always return contiguous increasing + addresses. This is true of unix sbrk. It does not hurt too much to + set it true anyway, since malloc copes with non-contiguities. + Setting it false when definitely non-contiguous saves time + and possibly wasted space it would take to discover this though. + +MORECORE_CANNOT_TRIM default: NOT defined + True if MORECORE cannot release space back to the system when given + negative arguments. This is generally necessary only if you are + using a hand-crafted MORECORE function that cannot handle negative + arguments. + +NO_SEGMENT_TRAVERSAL default: 0 + If non-zero, suppresses traversals of memory segments + returned by either MORECORE or CALL_MMAP. This disables + merging of segments that are contiguous, and selectively + releasing them to the OS if unused, but bounds execution times. + +HAVE_MMAP default: 1 (true) + True if this system supports mmap or an emulation of it. If so, and + HAVE_MORECORE is not true, MMAP is used for all system + allocation. If set and HAVE_MORECORE is true as well, MMAP is + primarily used to directly allocate very large blocks. It is also + used as a backup strategy in cases where MORECORE fails to provide + space from system. Note: A single call to MUNMAP is assumed to be + able to unmap memory that may have be allocated using multiple calls + to MMAP, so long as they are adjacent. + +HAVE_MREMAP default: 1 on linux, else 0 + If true realloc() uses mremap() to re-allocate large blocks and + extend or shrink allocation spaces. + +MMAP_CLEARS default: 1 except on WINCE. + True if mmap clears memory so calloc doesn't need to. This is true + for standard unix mmap using /dev/zero and on WIN32 except for WINCE. + +USE_BUILTIN_FFS default: 0 (i.e., not used) + Causes malloc to use the builtin ffs() function to compute indices. + Some compilers may recognize and intrinsify ffs to be faster than the + supplied C version. Also, the case of x86 using gcc is special-cased + to an asm instruction, so is already as fast as it can be, and so + this setting has no effect. Similarly for Win32 under recent MS compilers. + (On most x86s, the asm version is only slightly faster than the C version.) + +malloc_getpagesize default: derive from system includes, or 4096. + The system page size. To the extent possible, this malloc manages + memory from the system in page-size units. This may be (and + usually is) a function rather than a constant. This is ignored + if WIN32, where page size is determined using getSystemInfo during + initialization. + +USE_DEV_RANDOM default: 0 (i.e., not used) + Causes malloc to use /dev/random to initialize secure magic seed for + stamping footers. Otherwise, the current time is used. + +NO_MALLINFO default: 0 + If defined, don't compile "mallinfo". This can be a simple way + of dealing with mismatches between system declarations and + those in this file. + +MALLINFO_FIELD_TYPE default: size_t + The type of the fields in the mallinfo struct. This was originally + defined as "int" in SVID etc, but is more usefully defined as + size_t. The value is used only if HAVE_USR_INCLUDE_MALLOC_H is not set + +NO_MALLOC_STATS default: 0 + If defined, don't compile "malloc_stats". This avoids calls to + fprintf and bringing in stdio dependencies you might not want. + +REALLOC_ZERO_BYTES_FREES default: not defined + This should be set if a call to realloc with zero bytes should + be the same as a call to free. Some people think it should. Otherwise, + since this malloc returns a unique pointer for malloc(0), so does + realloc(p, 0). + +LACKS_UNISTD_H, LACKS_FCNTL_H, LACKS_SYS_PARAM_H, LACKS_SYS_MMAN_H +LACKS_STRINGS_H, LACKS_STRING_H, LACKS_SYS_TYPES_H, LACKS_ERRNO_H +LACKS_STDLIB_H LACKS_SCHED_H LACKS_TIME_H default: NOT defined unless on WIN32 + Define these if your system does not have these header files. + You might need to manually insert some of the declarations they provide. + +DEFAULT_GRANULARITY default: page size if MORECORE_CONTIGUOUS, + system_info.dwAllocationGranularity in WIN32, + otherwise 64K. + Also settable using mallopt(M_GRANULARITY, x) + The unit for allocating and deallocating memory from the system. On + most systems with contiguous MORECORE, there is no reason to + make this more than a page. However, systems with MMAP tend to + either require or encourage larger granularities. You can increase + this value to prevent system allocation functions to be called so + often, especially if they are slow. The value must be at least one + page and must be a power of two. Setting to 0 causes initialization + to either page size or win32 region size. (Note: In previous + versions of malloc, the equivalent of this option was called + "TOP_PAD") + +DEFAULT_TRIM_THRESHOLD default: 2MB + Also settable using mallopt(M_TRIM_THRESHOLD, x) + The maximum amount of unused top-most memory to keep before + releasing via malloc_trim in free(). Automatic trimming is mainly + useful in long-lived programs using contiguous MORECORE. Because + trimming via sbrk can be slow on some systems, and can sometimes be + wasteful (in cases where programs immediately afterward allocate + more large chunks) the value should be high enough so that your + overall system performance would improve by releasing this much + memory. As a rough guide, you might set to a value close to the + average size of a process (program) running on your system. + Releasing this much memory would allow such a process to run in + memory. Generally, it is worth tuning trim thresholds when a + program undergoes phases where several large chunks are allocated + and released in ways that can reuse each other's storage, perhaps + mixed with phases where there are no such chunks at all. The trim + value must be greater than page size to have any useful effect. To + disable trimming completely, you can set to MAX_SIZE_T. Note that the trick + some people use of mallocing a huge space and then freeing it at + program startup, in an attempt to reserve system memory, doesn't + have the intended effect under automatic trimming, since that memory + will immediately be returned to the system. + +DEFAULT_MMAP_THRESHOLD default: 256K + Also settable using mallopt(M_MMAP_THRESHOLD, x) + The request size threshold for using MMAP to directly service a + request. Requests of at least this size that cannot be allocated + using already-existing space will be serviced via mmap. (If enough + normal freed space already exists it is used instead.) Using mmap + segregates relatively large chunks of memory so that they can be + individually obtained and released from the host system. A request + serviced through mmap is never reused by any other request (at least + not directly; the system may just so happen to remap successive + requests to the same locations). Segregating space in this way has + the benefits that: Mmapped space can always be individually released + back to the system, which helps keep the system level memory demands + of a long-lived program low. Also, mapped memory doesn't become + `locked' between other chunks, as can happen with normally allocated + chunks, which means that even trimming via malloc_trim would not + release them. However, it has the disadvantage that the space + cannot be reclaimed, consolidated, and then used to service later + requests, as happens with normal chunks. The advantages of mmap + nearly always outweigh disadvantages for "large" chunks, but the + value of "large" may vary across systems. The default is an + empirically derived value that works well in most systems. You can + disable mmap by setting to MAX_SIZE_T. + +MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP + The number of consolidated frees between checks to release + unused segments when freeing. When using non-contiguous segments, + especially with multiple mspaces, checking only for topmost space + doesn't always suffice to trigger trimming. To compensate for this, + free() will, with a period of MAX_RELEASE_CHECK_RATE (or the + current number of segments, if greater) try to release unused + segments to the OS when freeing chunks that result in + consolidation. The best value for this parameter is a compromise + between slowing down frees with relatively costly checks that + rarely trigger versus holding on to unused memory. To effectively + disable, set to MAX_SIZE_T. This may lead to a very slight speed + improvement at the expense of carrying around more memory. +*/ + +/* Version identifier to allow people to support multiple versions */ +#ifndef DLMALLOC_VERSION +#define DLMALLOC_VERSION 20806 +#endif /* DLMALLOC_VERSION */ + +#ifndef DLMALLOC_EXPORT +#define DLMALLOC_EXPORT extern +#endif + +#ifndef WIN32 +#ifdef _WIN32 +#define WIN32 1 +#endif /* _WIN32 */ +#ifdef _WIN32_WCE +#define LACKS_FCNTL_H +#define WIN32 1 +#endif /* _WIN32_WCE */ +#endif /* WIN32 */ +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#define HAVE_MMAP 1 +#define HAVE_MORECORE 0 +#define LACKS_UNISTD_H +#define LACKS_SYS_PARAM_H +#define LACKS_SYS_MMAN_H +#define LACKS_STRING_H +#define LACKS_STRINGS_H +#define LACKS_SYS_TYPES_H +#define LACKS_ERRNO_H +#define LACKS_SCHED_H +#ifndef MALLOC_FAILURE_ACTION +#define MALLOC_FAILURE_ACTION +#endif /* MALLOC_FAILURE_ACTION */ +#ifndef MMAP_CLEARS +#ifdef _WIN32_WCE /* WINCE reportedly does not clear */ +#define MMAP_CLEARS 0 +#else +#define MMAP_CLEARS 1 +#endif /* _WIN32_WCE */ +#endif /*MMAP_CLEARS */ +#endif /* WIN32 */ + +#if defined(DARWIN) || defined(_DARWIN) +/* Mac OSX docs advise not to use sbrk; it seems better to use mmap */ +#ifndef HAVE_MORECORE +#define HAVE_MORECORE 0 +#define HAVE_MMAP 1 +/* OSX allocators provide 16 byte alignment */ +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)16U) +#endif +#endif /* HAVE_MORECORE */ +#endif /* DARWIN */ + +#ifndef LACKS_SYS_TYPES_H +#include /* For size_t */ +#endif /* LACKS_SYS_TYPES_H */ + +/* The maximum possible size_t value has all bits set */ +#define MAX_SIZE_T (~(size_t)0) + +#ifndef USE_LOCKS /* ensure true if spin or recursive locks set */ +#define USE_LOCKS ((defined(USE_SPIN_LOCKS) && USE_SPIN_LOCKS != 0) || \ + (defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0)) +#endif /* USE_LOCKS */ + +#if USE_LOCKS /* Spin locks for gcc >= 4.1, older gcc on x86, MSC >= 1310 */ +#if ((defined(__GNUC__) && \ + ((__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) || \ + defined(__i386__) || defined(__x86_64__))) || \ + (defined(_MSC_VER) && _MSC_VER>=1310)) +#ifndef USE_SPIN_LOCKS +#define USE_SPIN_LOCKS 1 +#endif /* USE_SPIN_LOCKS */ +#elif USE_SPIN_LOCKS +#error "USE_SPIN_LOCKS defined without implementation" +#endif /* ... locks available... */ +#elif !defined(USE_SPIN_LOCKS) +#define USE_SPIN_LOCKS 0 +#endif /* USE_LOCKS */ + +#ifndef ONLY_MSPACES +#define ONLY_MSPACES 0 +#endif /* ONLY_MSPACES */ +#ifndef MSPACES +#if ONLY_MSPACES +#define MSPACES 1 +#else /* ONLY_MSPACES */ +#define MSPACES 0 +#endif /* ONLY_MSPACES */ +#endif /* MSPACES */ +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)(2 * sizeof(void *))) +#endif /* MALLOC_ALIGNMENT */ +#ifndef FOOTERS +#define FOOTERS 0 +#endif /* FOOTERS */ +#ifndef ABORT +#define ABORT abort() +#endif /* ABORT */ +#ifndef ABORT_ON_ASSERT_FAILURE +#define ABORT_ON_ASSERT_FAILURE 1 +#endif /* ABORT_ON_ASSERT_FAILURE */ +#ifndef PROCEED_ON_ERROR +#define PROCEED_ON_ERROR 0 +#endif /* PROCEED_ON_ERROR */ + +#ifndef INSECURE +#define INSECURE 0 +#endif /* INSECURE */ +#ifndef MALLOC_INSPECT_ALL +#define MALLOC_INSPECT_ALL 0 +#endif /* MALLOC_INSPECT_ALL */ +#ifndef HAVE_MMAP +#define HAVE_MMAP 1 +#endif /* HAVE_MMAP */ +#ifndef MMAP_CLEARS +#define MMAP_CLEARS 1 +#endif /* MMAP_CLEARS */ +#ifndef HAVE_MREMAP +#ifdef linux +#define HAVE_MREMAP 1 +#define _GNU_SOURCE /* Turns on mremap() definition */ +#else /* linux */ +#define HAVE_MREMAP 0 +#endif /* linux */ +#endif /* HAVE_MREMAP */ +#ifndef MALLOC_FAILURE_ACTION +#define MALLOC_FAILURE_ACTION errno = ENOMEM; +#endif /* MALLOC_FAILURE_ACTION */ +#ifndef HAVE_MORECORE +#if ONLY_MSPACES +#define HAVE_MORECORE 0 +#else /* ONLY_MSPACES */ +#define HAVE_MORECORE 1 +#endif /* ONLY_MSPACES */ +#endif /* HAVE_MORECORE */ +#if !HAVE_MORECORE +#define MORECORE_CONTIGUOUS 0 +#else /* !HAVE_MORECORE */ +#define MORECORE_DEFAULT sbrk +#ifndef MORECORE_CONTIGUOUS +#define MORECORE_CONTIGUOUS 1 +#endif /* MORECORE_CONTIGUOUS */ +#endif /* HAVE_MORECORE */ +#ifndef DEFAULT_GRANULARITY +#if (MORECORE_CONTIGUOUS || defined(WIN32)) +#define DEFAULT_GRANULARITY (0) /* 0 means to compute in init_mparams */ +#else /* MORECORE_CONTIGUOUS */ +#define DEFAULT_GRANULARITY ((size_t)64U * (size_t)1024U) +#endif /* MORECORE_CONTIGUOUS */ +#endif /* DEFAULT_GRANULARITY */ +#ifndef DEFAULT_TRIM_THRESHOLD +#ifndef MORECORE_CANNOT_TRIM +#define DEFAULT_TRIM_THRESHOLD ((size_t)2U * (size_t)1024U * (size_t)1024U) +#else /* MORECORE_CANNOT_TRIM */ +#define DEFAULT_TRIM_THRESHOLD MAX_SIZE_T +#endif /* MORECORE_CANNOT_TRIM */ +#endif /* DEFAULT_TRIM_THRESHOLD */ +#ifndef DEFAULT_MMAP_THRESHOLD +#if HAVE_MMAP +#define DEFAULT_MMAP_THRESHOLD ((size_t)256U * (size_t)1024U) +#else /* HAVE_MMAP */ +#define DEFAULT_MMAP_THRESHOLD MAX_SIZE_T +#endif /* HAVE_MMAP */ +#endif /* DEFAULT_MMAP_THRESHOLD */ +#ifndef MAX_RELEASE_CHECK_RATE +#if HAVE_MMAP +#define MAX_RELEASE_CHECK_RATE 4095 +#else +#define MAX_RELEASE_CHECK_RATE MAX_SIZE_T +#endif /* HAVE_MMAP */ +#endif /* MAX_RELEASE_CHECK_RATE */ +#ifndef USE_BUILTIN_FFS +#define USE_BUILTIN_FFS 0 +#endif /* USE_BUILTIN_FFS */ +#ifndef USE_DEV_RANDOM +#define USE_DEV_RANDOM 0 +#endif /* USE_DEV_RANDOM */ +#ifndef NO_MALLINFO +#define NO_MALLINFO 0 +#endif /* NO_MALLINFO */ +#ifndef MALLINFO_FIELD_TYPE +#define MALLINFO_FIELD_TYPE size_t +#endif /* MALLINFO_FIELD_TYPE */ +#ifndef NO_MALLOC_STATS +#define NO_MALLOC_STATS 0 +#endif /* NO_MALLOC_STATS */ +#ifndef NO_SEGMENT_TRAVERSAL +#define NO_SEGMENT_TRAVERSAL 0 +#endif /* NO_SEGMENT_TRAVERSAL */ + +/* + mallopt tuning options. SVID/XPG defines four standard parameter + numbers for mallopt, normally defined in malloc.h. None of these + are used in this malloc, so setting them has no effect. But this + malloc does support the following options. +*/ + +#define M_TRIM_THRESHOLD (-1) +#define M_GRANULARITY (-2) +#define M_MMAP_THRESHOLD (-3) + +/* ------------------------ Mallinfo declarations ------------------------ */ + +#if !NO_MALLINFO +/* + This version of malloc supports the standard SVID/XPG mallinfo + routine that returns a struct containing usage properties and + statistics. It should work on any system that has a + /usr/include/malloc.h defining struct mallinfo. The main + declaration needed is the mallinfo struct that is returned (by-copy) + by mallinfo(). The malloinfo struct contains a bunch of fields that + are not even meaningful in this version of malloc. These fields are + are instead filled by mallinfo() with other numbers that might be of + interest. + + HAVE_USR_INCLUDE_MALLOC_H should be set if you have a + /usr/include/malloc.h file that includes a declaration of struct + mallinfo. If so, it is included; else a compliant version is + declared below. These must be precisely the same for mallinfo() to + work. The original SVID version of this struct, defined on most + systems with mallinfo, declares all fields as ints. But some others + define as unsigned long. If your system defines the fields using a + type of different width than listed here, you MUST #include your + system version and #define HAVE_USR_INCLUDE_MALLOC_H. +*/ + +/* #define HAVE_USR_INCLUDE_MALLOC_H */ + +#ifdef HAVE_USR_INCLUDE_MALLOC_H +#include "/usr/include/malloc.h" +#else /* HAVE_USR_INCLUDE_MALLOC_H */ +#ifndef STRUCT_MALLINFO_DECLARED +/* HP-UX (and others?) redefines mallinfo unless _STRUCT_MALLINFO is defined */ +#define _STRUCT_MALLINFO +#define STRUCT_MALLINFO_DECLARED 1 +struct mallinfo { + MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */ + MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */ + MALLINFO_FIELD_TYPE smblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */ + MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */ + MALLINFO_FIELD_TYPE fsmblks; /* always 0 */ + MALLINFO_FIELD_TYPE uordblks; /* total allocated space */ + MALLINFO_FIELD_TYPE fordblks; /* total free space */ + MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */ +}; +#endif /* STRUCT_MALLINFO_DECLARED */ +#endif /* HAVE_USR_INCLUDE_MALLOC_H */ +#endif /* NO_MALLINFO */ + +/* + Try to persuade compilers to inline. The most critical functions for + inlining are defined as macros, so these aren't used for them. +*/ + +#ifndef FORCEINLINE + #if defined(__GNUC__) +#define FORCEINLINE __inline __attribute__ ((always_inline)) + #elif defined(_MSC_VER) + #define FORCEINLINE __forceinline + #endif +#endif +#ifndef NOINLINE + #if defined(__GNUC__) + #define NOINLINE __attribute__ ((noinline)) + #elif defined(_MSC_VER) + #define NOINLINE __declspec(noinline) + #else + #define NOINLINE + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#ifndef FORCEINLINE + #define FORCEINLINE inline +#endif +#endif /* __cplusplus */ +#ifndef FORCEINLINE + #define FORCEINLINE +#endif + +#if !ONLY_MSPACES + +/* ------------------- Declarations of public routines ------------------- */ + +#ifndef USE_DL_PREFIX +#define dlcalloc calloc +#define dlfree free +#define dlmalloc malloc +#define dlmemalign memalign +#define dlposix_memalign posix_memalign +#define dlrealloc realloc +#define dlrealloc_in_place realloc_in_place +#define dlvalloc valloc +#define dlpvalloc pvalloc +#define dlmallinfo mallinfo +#define dlmallopt mallopt +#define dlmalloc_trim malloc_trim +#define dlmalloc_stats malloc_stats +#define dlmalloc_usable_size malloc_usable_size +#define dlmalloc_footprint malloc_footprint +#define dlmalloc_max_footprint malloc_max_footprint +#define dlmalloc_footprint_limit malloc_footprint_limit +#define dlmalloc_set_footprint_limit malloc_set_footprint_limit +#define dlmalloc_inspect_all malloc_inspect_all +#define dlindependent_calloc independent_calloc +#define dlindependent_comalloc independent_comalloc +#define dlbulk_free bulk_free +#endif /* USE_DL_PREFIX */ + +/* + malloc(size_t n) + Returns a pointer to a newly allocated chunk of at least n bytes, or + null if no space is available, in which case errno is set to ENOMEM + on ANSI C systems. + + If n is zero, malloc returns a minimum-sized chunk. (The minimum + size is 16 bytes on most 32bit systems, and 32 bytes on 64bit + systems.) Note that size_t is an unsigned type, so calls with + arguments that would be negative if signed are interpreted as + requests for huge amounts of space, which will often fail. The + maximum supported value of n differs across systems, but is in all + cases less than the maximum representable value of a size_t. +*/ +DLMALLOC_EXPORT void* dlmalloc(size_t); + +/* + free(void* p) + Releases the chunk of memory pointed to by p, that had been previously + allocated using malloc or a related routine such as realloc. + It has no effect if p is null. If p was not malloced or already + freed, free(p) will by default cause the current program to abort. +*/ +DLMALLOC_EXPORT void dlfree(void*); + +/* + calloc(size_t n_elements, size_t element_size); + Returns a pointer to n_elements * element_size bytes, with all locations + set to zero. +*/ +DLMALLOC_EXPORT void* dlcalloc(size_t, size_t); + +/* + realloc(void* p, size_t n) + Returns a pointer to a chunk of size n that contains the same data + as does chunk p up to the minimum of (n, p's size) bytes, or null + if no space is available. + + The returned pointer may or may not be the same as p. The algorithm + prefers extending p in most cases when possible, otherwise it + employs the equivalent of a malloc-copy-free sequence. + + If p is null, realloc is equivalent to malloc. + + If space is not available, realloc returns null, errno is set (if on + ANSI) and p is NOT freed. + + if n is for fewer bytes than already held by p, the newly unused + space is lopped off and freed if possible. realloc with a size + argument of zero (re)allocates a minimum-sized chunk. + + The old unix realloc convention of allowing the last-free'd chunk + to be used as an argument to realloc is not supported. +*/ +DLMALLOC_EXPORT void* dlrealloc(void*, size_t); + +/* + realloc_in_place(void* p, size_t n) + Resizes the space allocated for p to size n, only if this can be + done without moving p (i.e., only if there is adjacent space + available if n is greater than p's current allocated size, or n is + less than or equal to p's size). This may be used instead of plain + realloc if an alternative allocation strategy is needed upon failure + to expand space; for example, reallocation of a buffer that must be + memory-aligned or cleared. You can use realloc_in_place to trigger + these alternatives only when needed. + + Returns p if successful; otherwise null. +*/ +DLMALLOC_EXPORT void* dlrealloc_in_place(void*, size_t); + +/* + memalign(size_t alignment, size_t n); + Returns a pointer to a newly allocated chunk of n bytes, aligned + in accord with the alignment argument. + + The alignment argument should be a power of two. If the argument is + not a power of two, the nearest greater power is used. + 8-byte alignment is guaranteed by normal malloc calls, so don't + bother calling memalign with an argument of 8 or less. + + Overreliance on memalign is a sure way to fragment space. +*/ +DLMALLOC_EXPORT void* dlmemalign(size_t, size_t); + +/* + int posix_memalign(void** pp, size_t alignment, size_t n); + Allocates a chunk of n bytes, aligned in accord with the alignment + argument. Differs from memalign only in that it (1) assigns the + allocated memory to *pp rather than returning it, (2) fails and + returns EINVAL if the alignment is not a power of two (3) fails and + returns ENOMEM if memory cannot be allocated. +*/ +DLMALLOC_EXPORT int dlposix_memalign(void**, size_t, size_t); + +/* + valloc(size_t n); + Equivalent to memalign(pagesize, n), where pagesize is the page + size of the system. If the pagesize is unknown, 4096 is used. +*/ +DLMALLOC_EXPORT void* dlvalloc(size_t); + +/* + mallopt(int parameter_number, int parameter_value) + Sets tunable parameters The format is to provide a + (parameter-number, parameter-value) pair. mallopt then sets the + corresponding parameter to the argument value if it can (i.e., so + long as the value is meaningful), and returns 1 if successful else + 0. To workaround the fact that mallopt is specified to use int, + not size_t parameters, the value -1 is specially treated as the + maximum unsigned size_t value. + + SVID/XPG/ANSI defines four standard param numbers for mallopt, + normally defined in malloc.h. None of these are use in this malloc, + so setting them has no effect. But this malloc also supports other + options in mallopt. See below for details. Briefly, supported + parameters are as follows (listed defaults are for "typical" + configurations). + + Symbol param # default allowed param values + M_TRIM_THRESHOLD -1 2*1024*1024 any (-1 disables) + M_GRANULARITY -2 page size any power of 2 >= page size + M_MMAP_THRESHOLD -3 256*1024 any (or 0 if no MMAP support) +*/ +DLMALLOC_EXPORT int dlmallopt(int, int); + +/* + malloc_footprint(); + Returns the number of bytes obtained from the system. The total + number of bytes allocated by malloc, realloc etc., is less than this + value. Unlike mallinfo, this function returns only a precomputed + result, so can be called frequently to monitor memory consumption. + Even if locks are otherwise defined, this function does not use them, + so results might not be up to date. +*/ +DLMALLOC_EXPORT size_t dlmalloc_footprint(void); + +/* + malloc_max_footprint(); + Returns the maximum number of bytes obtained from the system. This + value will be greater than current footprint if deallocated space + has been reclaimed by the system. The peak number of bytes allocated + by malloc, realloc etc., is less than this value. Unlike mallinfo, + this function returns only a precomputed result, so can be called + frequently to monitor memory consumption. Even if locks are + otherwise defined, this function does not use them, so results might + not be up to date. +*/ +DLMALLOC_EXPORT size_t dlmalloc_max_footprint(void); + +/* + malloc_footprint_limit(); + Returns the number of bytes that the heap is allowed to obtain from + the system, returning the last value returned by + malloc_set_footprint_limit, or the maximum size_t value if + never set. The returned value reflects a permission. There is no + guarantee that this number of bytes can actually be obtained from + the system. +*/ +DLMALLOC_EXPORT size_t dlmalloc_footprint_limit(); + +/* + malloc_set_footprint_limit(); + Sets the maximum number of bytes to obtain from the system, causing + failure returns from malloc and related functions upon attempts to + exceed this value. The argument value may be subject to page + rounding to an enforceable limit; this actual value is returned. + Using an argument of the maximum possible size_t effectively + disables checks. If the argument is less than or equal to the + current malloc_footprint, then all future allocations that require + additional system memory will fail. However, invocation cannot + retroactively deallocate existing used memory. +*/ +DLMALLOC_EXPORT size_t dlmalloc_set_footprint_limit(size_t bytes); + +#if MALLOC_INSPECT_ALL +/* + malloc_inspect_all(void(*handler)(void *start, + void *end, + size_t used_bytes, + void* callback_arg), + void* arg); + Traverses the heap and calls the given handler for each managed + region, skipping all bytes that are (or may be) used for bookkeeping + purposes. Traversal does not include include chunks that have been + directly memory mapped. Each reported region begins at the start + address, and continues up to but not including the end address. The + first used_bytes of the region contain allocated data. If + used_bytes is zero, the region is unallocated. The handler is + invoked with the given callback argument. If locks are defined, they + are held during the entire traversal. It is a bad idea to invoke + other malloc functions from within the handler. + + For example, to count the number of in-use chunks with size greater + than 1000, you could write: + static int count = 0; + void count_chunks(void* start, void* end, size_t used, void* arg) { + if (used >= 1000) ++count; + } + then: + malloc_inspect_all(count_chunks, NULL); + + malloc_inspect_all is compiled only if MALLOC_INSPECT_ALL is defined. +*/ +DLMALLOC_EXPORT void dlmalloc_inspect_all(void(*handler)(void*, void *, size_t, void*), + void* arg); + +#endif /* MALLOC_INSPECT_ALL */ + +#if !NO_MALLINFO +/* + mallinfo() + Returns (by copy) a struct containing various summary statistics: + + arena: current total non-mmapped bytes allocated from system + ordblks: the number of free chunks + smblks: always zero. + hblks: current number of mmapped regions + hblkhd: total bytes held in mmapped regions + usmblks: the maximum total allocated space. This will be greater + than current total if trimming has occurred. + fsmblks: always zero + uordblks: current total allocated space (normal or mmapped) + fordblks: total free space + keepcost: the maximum number of bytes that could ideally be released + back to system via malloc_trim. ("ideally" means that + it ignores page restrictions etc.) + + Because these fields are ints, but internal bookkeeping may + be kept as longs, the reported values may wrap around zero and + thus be inaccurate. +*/ +DLMALLOC_EXPORT struct mallinfo dlmallinfo(void); +#endif /* NO_MALLINFO */ + +/* + independent_calloc(size_t n_elements, size_t element_size, void* chunks[]); + + independent_calloc is similar to calloc, but instead of returning a + single cleared space, it returns an array of pointers to n_elements + independent elements that can hold contents of size elem_size, each + of which starts out cleared, and can be independently freed, + realloc'ed etc. The elements are guaranteed to be adjacently + allocated (this is not guaranteed to occur with multiple callocs or + mallocs), which may also improve cache locality in some + applications. + + The "chunks" argument is optional (i.e., may be null, which is + probably the most typical usage). If it is null, the returned array + is itself dynamically allocated and should also be freed when it is + no longer needed. Otherwise, the chunks array must be of at least + n_elements in length. It is filled in with the pointers to the + chunks. + + In either case, independent_calloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and "chunks" + is null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be freed when it is no longer needed. This can be + done all at once using bulk_free. + + independent_calloc simplifies and speeds up implementations of many + kinds of pools. It may also be useful when constructing large data + structures that initially have a fixed number of fixed-sized nodes, + but the number is not known at compile time, and some of the nodes + may later need to be freed. For example: + + struct Node { int item; struct Node* next; }; + + struct Node* build_list() { + struct Node** pool; + int n = read_number_of_nodes_needed(); + if (n <= 0) return 0; + pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0); + if (pool == 0) die(); + // organize into a linked list... + struct Node* first = pool[0]; + for (i = 0; i < n-1; ++i) + pool[i]->next = pool[i+1]; + free(pool); // Can now free the array (or not, if it is needed later) + return first; + } +*/ +DLMALLOC_EXPORT void** dlindependent_calloc(size_t, size_t, void**); + +/* + independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]); + + independent_comalloc allocates, all at once, a set of n_elements + chunks with sizes indicated in the "sizes" array. It returns + an array of pointers to these elements, each of which can be + independently freed, realloc'ed etc. The elements are guaranteed to + be adjacently allocated (this is not guaranteed to occur with + multiple callocs or mallocs), which may also improve cache locality + in some applications. + + The "chunks" argument is optional (i.e., may be null). If it is null + the returned array is itself dynamically allocated and should also + be freed when it is no longer needed. Otherwise, the chunks array + must be of at least n_elements in length. It is filled in with the + pointers to the chunks. + + In either case, independent_comalloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and chunks is + null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be freed when it is no longer needed. This can be + done all at once using bulk_free. + + independent_comallac differs from independent_calloc in that each + element may have a different size, and also that it does not + automatically clear elements. + + independent_comalloc can be used to speed up allocation in cases + where several structs or objects must always be allocated at the + same time. For example: + + struct Head { ... } + struct Foot { ... } + + void send_message(char* msg) { + int msglen = strlen(msg); + size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) }; + void* chunks[3]; + if (independent_comalloc(3, sizes, chunks) == 0) + die(); + struct Head* head = (struct Head*)(chunks[0]); + char* body = (char*)(chunks[1]); + struct Foot* foot = (struct Foot*)(chunks[2]); + // ... + } + + In general though, independent_comalloc is worth using only for + larger values of n_elements. For small values, you probably won't + detect enough difference from series of malloc calls to bother. + + Overuse of independent_comalloc can increase overall memory usage, + since it cannot reuse existing noncontiguous small chunks that + might be available for some of the elements. +*/ +DLMALLOC_EXPORT void** dlindependent_comalloc(size_t, size_t*, void**); + +/* + bulk_free(void* array[], size_t n_elements) + Frees and clears (sets to null) each non-null pointer in the given + array. This is likely to be faster than freeing them one-by-one. + If footers are used, pointers that have been allocated in different + mspaces are not freed or cleared, and the count of all such pointers + is returned. For large arrays of pointers with poor locality, it + may be worthwhile to sort this array before calling bulk_free. +*/ +DLMALLOC_EXPORT size_t dlbulk_free(void**, size_t n_elements); + +/* + pvalloc(size_t n); + Equivalent to valloc(minimum-page-that-holds(n)), that is, + round up n to nearest pagesize. + */ +DLMALLOC_EXPORT void* dlpvalloc(size_t); + +/* + malloc_trim(size_t pad); + + If possible, gives memory back to the system (via negative arguments + to sbrk) if there is unused memory at the `high' end of the malloc + pool or in unused MMAP segments. You can call this after freeing + large blocks of memory to potentially reduce the system-level memory + requirements of a program. However, it cannot guarantee to reduce + memory. Under some allocation patterns, some large free blocks of + memory will be locked between two used chunks, so they cannot be + given back to the system. + + The `pad' argument to malloc_trim represents the amount of free + trailing space to leave untrimmed. If this argument is zero, only + the minimum amount of memory to maintain internal data structures + will be left. Non-zero arguments can be supplied to maintain enough + trailing space to service future expected allocations without having + to re-obtain memory from the system. + + Malloc_trim returns 1 if it actually released any memory, else 0. +*/ +DLMALLOC_EXPORT int dlmalloc_trim(size_t); + +/* + malloc_stats(); + Prints on stderr the amount of space obtained from the system (both + via sbrk and mmap), the maximum amount (which may be more than + current if malloc_trim and/or munmap got called), and the current + number of bytes allocated via malloc (or realloc, etc) but not yet + freed. Note that this is the number of bytes allocated, not the + number requested. It will be larger than the number requested + because of alignment and bookkeeping overhead. Because it includes + alignment wastage as being in use, this figure may be greater than + zero even when no user-level chunks are allocated. + + The reported current and maximum system memory can be inaccurate if + a program makes other calls to system memory allocation functions + (normally sbrk) outside of malloc. + + malloc_stats prints only the most commonly interesting statistics. + More information can be obtained by calling mallinfo. +*/ +DLMALLOC_EXPORT void dlmalloc_stats(void); + +/* + malloc_usable_size(void* p); + + Returns the number of bytes you can actually use in + an allocated chunk, which may be more than you requested (although + often not) due to alignment and minimum size constraints. + You can use this many bytes without worrying about + overwriting other allocated objects. This is not a particularly great + programming practice. malloc_usable_size can be more useful in + debugging and assertions, for example: + + p = malloc(n); + assert(malloc_usable_size(p) >= 256); +*/ +size_t dlmalloc_usable_size(void*); + +#endif /* ONLY_MSPACES */ + +#if MSPACES + +/* + mspace is an opaque type representing an independent + region of space that supports mspace_malloc, etc. +*/ +typedef void* mspace; + +/* + create_mspace creates and returns a new independent space with the + given initial capacity, or, if 0, the default granularity size. It + returns null if there is no system memory available to create the + space. If argument locked is non-zero, the space uses a separate + lock to control access. The capacity of the space will grow + dynamically as needed to service mspace_malloc requests. You can + control the sizes of incremental increases of this space by + compiling with a different DEFAULT_GRANULARITY or dynamically + setting with mallopt(M_GRANULARITY, value). +*/ +DLMALLOC_EXPORT mspace create_mspace(size_t capacity, int locked); + +/* + destroy_mspace destroys the given space, and attempts to return all + of its memory back to the system, returning the total number of + bytes freed. After destruction, the results of access to all memory + used by the space become undefined. +*/ +DLMALLOC_EXPORT size_t destroy_mspace(mspace msp); + +/* + create_mspace_with_base uses the memory supplied as the initial base + of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this + space is used for bookkeeping, so the capacity must be at least this + large. (Otherwise 0 is returned.) When this initial space is + exhausted, additional memory will be obtained from the system. + Destroying this space will deallocate all additionally allocated + space (if possible) but not the initial base. +*/ +DLMALLOC_EXPORT mspace create_mspace_with_base(void* base, size_t capacity, int locked); + +/* + mspace_track_large_chunks controls whether requests for large chunks + are allocated in their own untracked mmapped regions, separate from + others in this mspace. By default large chunks are not tracked, + which reduces fragmentation. However, such chunks are not + necessarily released to the system upon destroy_mspace. Enabling + tracking by setting to true may increase fragmentation, but avoids + leakage when relying on destroy_mspace to release all memory + allocated using this space. The function returns the previous + setting. +*/ +DLMALLOC_EXPORT int mspace_track_large_chunks(mspace msp, int enable); + + +/* + mspace_malloc behaves as malloc, but operates within + the given space. +*/ +DLMALLOC_EXPORT void* mspace_malloc(mspace msp, size_t bytes); + +/* + mspace_free behaves as free, but operates within + the given space. + + If compiled with FOOTERS==1, mspace_free is not actually needed. + free may be called instead of mspace_free because freed chunks from + any space are handled by their originating spaces. +*/ +DLMALLOC_EXPORT void mspace_free(mspace msp, void* mem); + +/* + mspace_realloc behaves as realloc, but operates within + the given space. + + If compiled with FOOTERS==1, mspace_realloc is not actually + needed. realloc may be called instead of mspace_realloc because + realloced chunks from any space are handled by their originating + spaces. +*/ +DLMALLOC_EXPORT void* mspace_realloc(mspace msp, void* mem, size_t newsize); + +/* + mspace_calloc behaves as calloc, but operates within + the given space. +*/ +DLMALLOC_EXPORT void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size); + +/* + mspace_memalign behaves as memalign, but operates within + the given space. +*/ +DLMALLOC_EXPORT void* mspace_memalign(mspace msp, size_t alignment, size_t bytes); + +/* + mspace_independent_calloc behaves as independent_calloc, but + operates within the given space. +*/ +DLMALLOC_EXPORT void** mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]); + +/* + mspace_independent_comalloc behaves as independent_comalloc, but + operates within the given space. +*/ +DLMALLOC_EXPORT void** mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]); + +/* + mspace_footprint() returns the number of bytes obtained from the + system for this space. +*/ +DLMALLOC_EXPORT size_t mspace_footprint(mspace msp); + +/* + mspace_max_footprint() returns the peak number of bytes obtained from the + system for this space. +*/ +DLMALLOC_EXPORT size_t mspace_max_footprint(mspace msp); + + +#if !NO_MALLINFO +/* + mspace_mallinfo behaves as mallinfo, but reports properties of + the given space. +*/ +DLMALLOC_EXPORT struct mallinfo mspace_mallinfo(mspace msp); +#endif /* NO_MALLINFO */ + +/* + malloc_usable_size(void* p) behaves the same as malloc_usable_size; +*/ +DLMALLOC_EXPORT size_t mspace_usable_size(const void* mem); + +/* + mspace_malloc_stats behaves as malloc_stats, but reports + properties of the given space. +*/ +DLMALLOC_EXPORT void mspace_malloc_stats(mspace msp); + +/* + mspace_trim behaves as malloc_trim, but + operates within the given space. +*/ +DLMALLOC_EXPORT int mspace_trim(mspace msp, size_t pad); + +/* + An alias for mallopt. +*/ +DLMALLOC_EXPORT int mspace_mallopt(int, int); + +#endif /* MSPACES */ + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif /* __cplusplus */ + +/* + ======================================================================== + To make a fully customizable malloc.h header file, cut everything + above this line, put into file malloc.h, edit to suit, and #include it + on the next line, as well as in programs that use this malloc. + ======================================================================== +*/ + +/* #include "malloc.h" */ + +/*------------------------------ internal #includes ---------------------- */ + +#ifdef _MSC_VER +#pragma warning( disable : 4146 ) /* no "unsigned" warnings */ +#endif /* _MSC_VER */ +#if !NO_MALLOC_STATS +#include /* for printing in malloc_stats */ +#endif /* NO_MALLOC_STATS */ +#ifndef LACKS_ERRNO_H +#include /* for MALLOC_FAILURE_ACTION */ +#endif /* LACKS_ERRNO_H */ +#ifdef DEBUG +#if ABORT_ON_ASSERT_FAILURE +#undef assert +#define assert(x) if(!(x)) ABORT +#else /* ABORT_ON_ASSERT_FAILURE */ +#include +#endif /* ABORT_ON_ASSERT_FAILURE */ +#else /* DEBUG */ +#ifndef assert +#define assert(x) +#endif +#define DEBUG 0 +#endif /* DEBUG */ +#if !defined(WIN32) && !defined(LACKS_TIME_H) +#include /* for magic initialization */ +#endif /* WIN32 */ +#ifndef LACKS_STDLIB_H +#include /* for abort() */ +#endif /* LACKS_STDLIB_H */ +#ifndef LACKS_STRING_H +#include /* for memset etc */ +#endif /* LACKS_STRING_H */ +#if USE_BUILTIN_FFS +#ifndef LACKS_STRINGS_H +#include /* for ffs */ +#endif /* LACKS_STRINGS_H */ +#endif /* USE_BUILTIN_FFS */ +#if HAVE_MMAP +#ifndef LACKS_SYS_MMAN_H +/* On some versions of linux, mremap decl in mman.h needs __USE_GNU set */ +#if (defined(linux) && !defined(__USE_GNU)) +#define __USE_GNU 1 +#include /* for mmap */ +#undef __USE_GNU +#else +#include /* for mmap */ +#endif /* linux */ +#endif /* LACKS_SYS_MMAN_H */ +#ifndef LACKS_FCNTL_H +#include +#endif /* LACKS_FCNTL_H */ +#endif /* HAVE_MMAP */ +#ifndef LACKS_UNISTD_H +#include /* for sbrk, sysconf */ +#else /* LACKS_UNISTD_H */ +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) +extern void* sbrk(ptrdiff_t); +#endif /* FreeBSD etc */ +#endif /* LACKS_UNISTD_H */ + +/* Declarations for locking */ +#if USE_LOCKS +#ifndef WIN32 +#if defined (__SVR4) && defined (__sun) /* solaris */ +#include +#elif !defined(LACKS_SCHED_H) +#include +#endif /* solaris or LACKS_SCHED_H */ +#if (defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0) || !USE_SPIN_LOCKS +#include +#endif /* USE_RECURSIVE_LOCKS ... */ +#elif defined(_MSC_VER) +#ifndef _M_AMD64 +/* These are already defined on AMD64 builds */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +LONG __cdecl _InterlockedCompareExchange(LONG volatile *Dest, LONG Exchange, LONG Comp); +LONG __cdecl _InterlockedExchange(LONG volatile *Target, LONG Value); +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _M_AMD64 */ +#pragma intrinsic (_InterlockedCompareExchange) +#pragma intrinsic (_InterlockedExchange) +#define interlockedcompareexchange _InterlockedCompareExchange +#define interlockedexchange _InterlockedExchange +#elif defined(WIN32) && defined(__GNUC__) +#define interlockedcompareexchange(a, b, c) __sync_val_compare_and_swap(a, c, b) +#define interlockedexchange __sync_lock_test_and_set +#endif /* Win32 */ +#else /* USE_LOCKS */ +#endif /* USE_LOCKS */ + +#ifndef LOCK_AT_FORK +#define LOCK_AT_FORK 0 +#endif + +/* Declarations for bit scanning on win32 */ +#if defined(_MSC_VER) && _MSC_VER>=1300 +#ifndef BitScanForward /* Try to avoid pulling in WinNT.h */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +unsigned char _BitScanForward(unsigned long *index, unsigned long mask); +unsigned char _BitScanReverse(unsigned long *index, unsigned long mask); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#define BitScanForward _BitScanForward +#define BitScanReverse _BitScanReverse +#pragma intrinsic(_BitScanForward) +#pragma intrinsic(_BitScanReverse) +#endif /* BitScanForward */ +#endif /* defined(_MSC_VER) && _MSC_VER>=1300 */ + +#ifndef WIN32 +#ifndef malloc_getpagesize +# ifdef _SC_PAGESIZE /* some SVR4 systems omit an underscore */ +# ifndef _SC_PAGE_SIZE +# define _SC_PAGE_SIZE _SC_PAGESIZE +# endif +# endif +# ifdef _SC_PAGE_SIZE +# define malloc_getpagesize sysconf(_SC_PAGE_SIZE) +# else +# if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE) + extern size_t getpagesize(); +# define malloc_getpagesize getpagesize() +# else +# ifdef WIN32 /* use supplied emulation of getpagesize */ +# define malloc_getpagesize getpagesize() +# else +# ifndef LACKS_SYS_PARAM_H +# include +# endif +# ifdef EXEC_PAGESIZE +# define malloc_getpagesize EXEC_PAGESIZE +# else +# ifdef NBPG +# ifndef CLSIZE +# define malloc_getpagesize NBPG +# else +# define malloc_getpagesize (NBPG * CLSIZE) +# endif +# else +# ifdef NBPC +# define malloc_getpagesize NBPC +# else +# ifdef PAGESIZE +# define malloc_getpagesize PAGESIZE +# else /* just guess */ +# define malloc_getpagesize ((size_t)4096U) +# endif +# endif +# endif +# endif +# endif +# endif +# endif +#endif +#endif + +/* ------------------- size_t and alignment properties -------------------- */ + +/* The byte and bit size of a size_t */ +#define SIZE_T_SIZE (sizeof(size_t)) +#define SIZE_T_BITSIZE (sizeof(size_t) << 3) + +/* Some constants coerced to size_t */ +/* Annoying but necessary to avoid errors on some platforms */ +#define SIZE_T_ZERO ((size_t)0) +#define SIZE_T_ONE ((size_t)1) +#define SIZE_T_TWO ((size_t)2) +#define SIZE_T_FOUR ((size_t)4) +#define TWO_SIZE_T_SIZES (SIZE_T_SIZE<<1) +#define FOUR_SIZE_T_SIZES (SIZE_T_SIZE<<2) +#define SIX_SIZE_T_SIZES (FOUR_SIZE_T_SIZES+TWO_SIZE_T_SIZES) +#define HALF_MAX_SIZE_T (MAX_SIZE_T / 2U) + +/* The bit mask value corresponding to MALLOC_ALIGNMENT */ +#define CHUNK_ALIGN_MASK (MALLOC_ALIGNMENT - SIZE_T_ONE) + +/* True if address a has acceptable alignment */ +#define is_aligned(A) (((size_t)((A)) & (CHUNK_ALIGN_MASK)) == 0) + +/* the number of bytes to offset an address to align it */ +#define align_offset(A)\ + ((((size_t)(A) & CHUNK_ALIGN_MASK) == 0)? 0 :\ + ((MALLOC_ALIGNMENT - ((size_t)(A) & CHUNK_ALIGN_MASK)) & CHUNK_ALIGN_MASK)) + +/* -------------------------- MMAP preliminaries ------------------------- */ + +/* + If HAVE_MORECORE or HAVE_MMAP are false, we just define calls and + checks to fail so compiler optimizer can delete code rather than + using so many "#if"s. +*/ + + +/* MORECORE and MMAP must return MFAIL on failure */ +#define MFAIL ((void*)(MAX_SIZE_T)) +#define CMFAIL ((char*)(MFAIL)) /* defined for convenience */ + +#if HAVE_MMAP + +#ifndef WIN32 +#define MUNMAP_DEFAULT(a, s) munmap((a), (s)) +#define MMAP_PROT (PROT_READ|PROT_WRITE) +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +#define MAP_ANONYMOUS MAP_ANON +#endif /* MAP_ANON */ +#ifdef MAP_ANONYMOUS +#define MMAP_FLAGS (MAP_PRIVATE|MAP_ANONYMOUS) +#define MMAP_DEFAULT(s) mmap(0, (s), MMAP_PROT, MMAP_FLAGS, -1, 0) +#else /* MAP_ANONYMOUS */ +/* + Nearly all versions of mmap support MAP_ANONYMOUS, so the following + is unlikely to be needed, but is supplied just in case. +*/ +#define MMAP_FLAGS (MAP_PRIVATE) +static int dev_zero_fd = -1; /* Cached file descriptor for /dev/zero. */ +#define MMAP_DEFAULT(s) ((dev_zero_fd < 0) ? \ + (dev_zero_fd = open("/dev/zero", O_RDWR), \ + mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) : \ + mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) +#endif /* MAP_ANONYMOUS */ + +#define DIRECT_MMAP_DEFAULT(s) MMAP_DEFAULT(s) + +#else /* WIN32 */ + +/* Win32 MMAP via VirtualAlloc */ +static FORCEINLINE void* win32mmap(size_t size) { + void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + return (ptr != 0)? ptr: MFAIL; +} + +/* For direct MMAP, use MEM_TOP_DOWN to minimize interference */ +static FORCEINLINE void* win32direct_mmap(size_t size) { + void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN, + PAGE_READWRITE); + return (ptr != 0)? ptr: MFAIL; +} + +/* This function supports releasing coalesed segments */ +static FORCEINLINE int win32munmap(void* ptr, size_t size) { + MEMORY_BASIC_INFORMATION minfo; + char* cptr = (char*)ptr; + while (size) { + if (VirtualQuery(cptr, &minfo, sizeof(minfo)) == 0) + return -1; + if (minfo.BaseAddress != cptr || minfo.AllocationBase != cptr || + minfo.State != MEM_COMMIT || minfo.RegionSize > size) + return -1; + if (VirtualFree(cptr, 0, MEM_RELEASE) == 0) + return -1; + cptr += minfo.RegionSize; + size -= minfo.RegionSize; + } + return 0; +} + +#define MMAP_DEFAULT(s) win32mmap(s) +#define MUNMAP_DEFAULT(a, s) win32munmap((a), (s)) +#define DIRECT_MMAP_DEFAULT(s) win32direct_mmap(s) +#endif /* WIN32 */ +#endif /* HAVE_MMAP */ + +#if HAVE_MREMAP +#ifndef WIN32 +#define MREMAP_DEFAULT(addr, osz, nsz, mv) mremap((addr), (osz), (nsz), (mv)) +#endif /* WIN32 */ +#endif /* HAVE_MREMAP */ + +/** + * Define CALL_MORECORE + */ +#if HAVE_MORECORE + #ifdef MORECORE + #define CALL_MORECORE(S) MORECORE(S) + #else /* MORECORE */ + #define CALL_MORECORE(S) MORECORE_DEFAULT(S) + #endif /* MORECORE */ +#else /* HAVE_MORECORE */ + #define CALL_MORECORE(S) MFAIL +#endif /* HAVE_MORECORE */ + +/** + * Define CALL_MMAP/CALL_MUNMAP/CALL_DIRECT_MMAP + */ +#if HAVE_MMAP + #define USE_MMAP_BIT (SIZE_T_ONE) + + #ifdef MMAP + #define CALL_MMAP(s) MMAP(s) + #else /* MMAP */ + #define CALL_MMAP(s) MMAP_DEFAULT(s) + #endif /* MMAP */ + #ifdef MUNMAP + #define CALL_MUNMAP(a, s) MUNMAP((a), (s)) + #else /* MUNMAP */ + #define CALL_MUNMAP(a, s) MUNMAP_DEFAULT((a), (s)) + #endif /* MUNMAP */ + #ifdef DIRECT_MMAP + #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) + #else /* DIRECT_MMAP */ + #define CALL_DIRECT_MMAP(s) DIRECT_MMAP_DEFAULT(s) + #endif /* DIRECT_MMAP */ +#else /* HAVE_MMAP */ + #define USE_MMAP_BIT (SIZE_T_ZERO) + + #define MMAP(s) MFAIL + #define MUNMAP(a, s) (-1) + #define DIRECT_MMAP(s) MFAIL + #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) + #define CALL_MMAP(s) MMAP(s) + #define CALL_MUNMAP(a, s) MUNMAP((a), (s)) +#endif /* HAVE_MMAP */ + +/** + * Define CALL_MREMAP + */ +#if HAVE_MMAP && HAVE_MREMAP + #ifdef MREMAP + #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP((addr), (osz), (nsz), (mv)) + #else /* MREMAP */ + #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP_DEFAULT((addr), (osz), (nsz), (mv)) + #endif /* MREMAP */ +#else /* HAVE_MMAP && HAVE_MREMAP */ + #define CALL_MREMAP(addr, osz, nsz, mv) MFAIL +#endif /* HAVE_MMAP && HAVE_MREMAP */ + +/* mstate bit set if continguous morecore disabled or failed */ +#define USE_NONCONTIGUOUS_BIT (4U) + +/* segment bit set in create_mspace_with_base */ +#define EXTERN_BIT (8U) + + +/* --------------------------- Lock preliminaries ------------------------ */ + +/* + When locks are defined, there is one global lock, plus + one per-mspace lock. + + The global lock_ensures that mparams.magic and other unique + mparams values are initialized only once. It also protects + sequences of calls to MORECORE. In many cases sys_alloc requires + two calls, that should not be interleaved with calls by other + threads. This does not protect against direct calls to MORECORE + by other threads not using this lock, so there is still code to + cope the best we can on interference. + + Per-mspace locks surround calls to malloc, free, etc. + By default, locks are simple non-reentrant mutexes. + + Because lock-protected regions generally have bounded times, it is + OK to use the supplied simple spinlocks. Spinlocks are likely to + improve performance for lightly contended applications, but worsen + performance under heavy contention. + + If USE_LOCKS is > 1, the definitions of lock routines here are + bypassed, in which case you will need to define the type MLOCK_T, + and at least INITIAL_LOCK, DESTROY_LOCK, ACQUIRE_LOCK, RELEASE_LOCK + and TRY_LOCK. You must also declare a + static MLOCK_T malloc_global_mutex = { initialization values };. + +*/ + +#if !USE_LOCKS +#define USE_LOCK_BIT (0U) +#define INITIAL_LOCK(l) (0) +#define DESTROY_LOCK(l) (0) +#define ACQUIRE_MALLOC_GLOBAL_LOCK() +#define RELEASE_MALLOC_GLOBAL_LOCK() + +#else +#if USE_LOCKS > 1 +/* ----------------------- User-defined locks ------------------------ */ +/* Define your own lock implementation here */ +/* #define INITIAL_LOCK(lk) ... */ +/* #define DESTROY_LOCK(lk) ... */ +/* #define ACQUIRE_LOCK(lk) ... */ +/* #define RELEASE_LOCK(lk) ... */ +/* #define TRY_LOCK(lk) ... */ +/* static MLOCK_T malloc_global_mutex = ... */ + +#elif USE_SPIN_LOCKS + +/* First, define CAS_LOCK and CLEAR_LOCK on ints */ +/* Note CAS_LOCK defined to return 0 on success */ + +#if defined(__GNUC__)&& (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) +#define CAS_LOCK(sl) __sync_lock_test_and_set(sl, 1) +#define CLEAR_LOCK(sl) __sync_lock_release(sl) + +#elif (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) +/* Custom spin locks for older gcc on x86 */ +static FORCEINLINE int x86_cas_lock(int *sl) { + int ret; + int val = 1; + int cmp = 0; + __asm__ __volatile__ ("lock; cmpxchgl %1, %2" + : "=a" (ret) + : "r" (val), "m" (*(sl)), "0"(cmp) + : "memory", "cc"); + return ret; +} + +static FORCEINLINE void x86_clear_lock(int* sl) { + assert(*sl != 0); + int prev = 0; + int ret; + __asm__ __volatile__ ("lock; xchgl %0, %1" + : "=r" (ret) + : "m" (*(sl)), "0"(prev) + : "memory"); +} + +#define CAS_LOCK(sl) x86_cas_lock(sl) +#define CLEAR_LOCK(sl) x86_clear_lock(sl) + +#else /* Win32 MSC */ +#define CAS_LOCK(sl) interlockedexchange(sl, (LONG)1) +#define CLEAR_LOCK(sl) interlockedexchange (sl, (LONG)0) + +#endif /* ... gcc spins locks ... */ + +/* How to yield for a spin lock */ +#define SPINS_PER_YIELD 63 +#if defined(_MSC_VER) +#define SLEEP_EX_DURATION 50 /* delay for yield/sleep */ +#define SPIN_LOCK_YIELD SleepEx(SLEEP_EX_DURATION, FALSE) +#elif defined (__SVR4) && defined (__sun) /* solaris */ +#define SPIN_LOCK_YIELD thr_yield(); +#elif !defined(LACKS_SCHED_H) +#define SPIN_LOCK_YIELD sched_yield(); +#else +#define SPIN_LOCK_YIELD +#endif /* ... yield ... */ + +#if !defined(USE_RECURSIVE_LOCKS) || USE_RECURSIVE_LOCKS == 0 +/* Plain spin locks use single word (embedded in malloc_states) */ +static int spin_acquire_lock(int *sl) { + int spins = 0; + while (*(volatile int *)sl != 0 || CAS_LOCK(sl)) { + if ((++spins & SPINS_PER_YIELD) == 0) { + SPIN_LOCK_YIELD; + } + } + return 0; +} + +#define MLOCK_T int +#define TRY_LOCK(sl) !CAS_LOCK(sl) +#define RELEASE_LOCK(sl) CLEAR_LOCK(sl) +#define ACQUIRE_LOCK(sl) (CAS_LOCK(sl)? spin_acquire_lock(sl) : 0) +#define INITIAL_LOCK(sl) (*sl = 0) +#define DESTROY_LOCK(sl) (0) +static MLOCK_T malloc_global_mutex = 0; + +#else /* USE_RECURSIVE_LOCKS */ +/* types for lock owners */ +#ifdef WIN32 +#define THREAD_ID_T DWORD +#define CURRENT_THREAD GetCurrentThreadId() +#define EQ_OWNER(X,Y) ((X) == (Y)) +#else +/* + Note: the following assume that pthread_t is a type that can be + initialized to (casted) zero. If this is not the case, you will need to + somehow redefine these or not use spin locks. +*/ +#define THREAD_ID_T pthread_t +#define CURRENT_THREAD pthread_self() +#define EQ_OWNER(X,Y) pthread_equal(X, Y) +#endif + +struct malloc_recursive_lock { + int sl; + unsigned int c; + THREAD_ID_T threadid; +}; + +#define MLOCK_T struct malloc_recursive_lock +static MLOCK_T malloc_global_mutex = { 0, 0, (THREAD_ID_T)0}; + +static FORCEINLINE void recursive_release_lock(MLOCK_T *lk) { + assert(lk->sl != 0); + if (--lk->c == 0) { + CLEAR_LOCK(&lk->sl); + } +} + +static FORCEINLINE int recursive_acquire_lock(MLOCK_T *lk) { + THREAD_ID_T mythreadid = CURRENT_THREAD; + int spins = 0; + for (;;) { + if (*((volatile int *)(&lk->sl)) == 0) { + if (!CAS_LOCK(&lk->sl)) { + lk->threadid = mythreadid; + lk->c = 1; + return 0; + } + } + else if (EQ_OWNER(lk->threadid, mythreadid)) { + ++lk->c; + return 0; + } + if ((++spins & SPINS_PER_YIELD) == 0) { + SPIN_LOCK_YIELD; + } + } +} + +static FORCEINLINE int recursive_try_lock(MLOCK_T *lk) { + THREAD_ID_T mythreadid = CURRENT_THREAD; + if (*((volatile int *)(&lk->sl)) == 0) { + if (!CAS_LOCK(&lk->sl)) { + lk->threadid = mythreadid; + lk->c = 1; + return 1; + } + } + else if (EQ_OWNER(lk->threadid, mythreadid)) { + ++lk->c; + return 1; + } + return 0; +} + +#define RELEASE_LOCK(lk) recursive_release_lock(lk) +#define TRY_LOCK(lk) recursive_try_lock(lk) +#define ACQUIRE_LOCK(lk) recursive_acquire_lock(lk) +#define INITIAL_LOCK(lk) ((lk)->threadid = (THREAD_ID_T)0, (lk)->sl = 0, (lk)->c = 0) +#define DESTROY_LOCK(lk) (0) +#endif /* USE_RECURSIVE_LOCKS */ + +#elif defined(WIN32) /* Win32 critical sections */ +#define MLOCK_T CRITICAL_SECTION +#define ACQUIRE_LOCK(lk) (EnterCriticalSection(lk), 0) +#define RELEASE_LOCK(lk) LeaveCriticalSection(lk) +#define TRY_LOCK(lk) TryEnterCriticalSection(lk) +#define INITIAL_LOCK(lk) (!InitializeCriticalSectionAndSpinCount((lk), 0x80000000|4000)) +#define DESTROY_LOCK(lk) (DeleteCriticalSection(lk), 0) +#define NEED_GLOBAL_LOCK_INIT + +static MLOCK_T malloc_global_mutex; +static volatile LONG malloc_global_mutex_status; + +/* Use spin loop to initialize global lock */ +static void init_malloc_global_mutex() { + for (;;) { + long stat = malloc_global_mutex_status; + if (stat > 0) + return; + /* transition to < 0 while initializing, then to > 0) */ + if (stat == 0 && + interlockedcompareexchange(&malloc_global_mutex_status, (LONG)-1, (LONG)0) == 0) { + InitializeCriticalSection(&malloc_global_mutex); + interlockedexchange(&malloc_global_mutex_status, (LONG)1); + return; + } + SleepEx(0, FALSE); + } +} + +#else /* pthreads-based locks */ +#define MLOCK_T pthread_mutex_t +#define ACQUIRE_LOCK(lk) pthread_mutex_lock(lk) +#define RELEASE_LOCK(lk) pthread_mutex_unlock(lk) +#define TRY_LOCK(lk) (!pthread_mutex_trylock(lk)) +#define INITIAL_LOCK(lk) pthread_init_lock(lk) +#define DESTROY_LOCK(lk) pthread_mutex_destroy(lk) + +#if defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0 && defined(linux) && !defined(PTHREAD_MUTEX_RECURSIVE) +/* Cope with old-style linux recursive lock initialization by adding */ +/* skipped internal declaration from pthread.h */ +extern int pthread_mutexattr_setkind_np __P ((pthread_mutexattr_t *__attr, + int __kind)); +#define PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP +#define pthread_mutexattr_settype(x,y) pthread_mutexattr_setkind_np(x,y) +#endif /* USE_RECURSIVE_LOCKS ... */ + +static MLOCK_T malloc_global_mutex = PTHREAD_MUTEX_INITIALIZER; + +static int pthread_init_lock (MLOCK_T *lk) { + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr)) return 1; +#if defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0 + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) return 1; +#endif + if (pthread_mutex_init(lk, &attr)) return 1; + if (pthread_mutexattr_destroy(&attr)) return 1; + return 0; +} + +#endif /* ... lock types ... */ + +/* Common code for all lock types */ +#define USE_LOCK_BIT (2U) + +#ifndef ACQUIRE_MALLOC_GLOBAL_LOCK +#define ACQUIRE_MALLOC_GLOBAL_LOCK() ACQUIRE_LOCK(&malloc_global_mutex); +#endif + +#ifndef RELEASE_MALLOC_GLOBAL_LOCK +#define RELEASE_MALLOC_GLOBAL_LOCK() RELEASE_LOCK(&malloc_global_mutex); +#endif + +#endif /* USE_LOCKS */ + +/* ----------------------- Chunk representations ------------------------ */ + +/* + (The following includes lightly edited explanations by Colin Plumb.) + + The malloc_chunk declaration below is misleading (but accurate and + necessary). It declares a "view" into memory allowing access to + necessary fields at known offsets from a given base. + + Chunks of memory are maintained using a `boundary tag' method as + originally described by Knuth. (See the paper by Paul Wilson + ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a survey of such + techniques.) Sizes of free chunks are stored both in the front of + each chunk and at the end. This makes consolidating fragmented + chunks into bigger chunks fast. The head fields also hold bits + representing whether chunks are free or in use. + + Here are some pictures to make it clearer. They are "exploded" to + show that the state of a chunk can be thought of as extending from + the high 31 bits of the head field of its header through the + prev_foot and PINUSE_BIT bit of the following chunk header. + + A chunk that's in use looks like: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk (if P = 0) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| + | Size of this chunk 1| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + +- -+ + | | + +- -+ + | : + +- size - sizeof(size_t) available payload bytes -+ + : | + chunk-> +- -+ + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1| + | Size of next chunk (may or may not be in use) | +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + And if it's free, it looks like this: + + chunk-> +- -+ + | User payload (must be in use, or we would have merged!) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| + | Size of this chunk 0| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Prev pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | : + +- size - sizeof(struct chunk) unused bytes -+ + : | + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of this chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0| + | Size of next chunk (must be in use, or we would have merged)| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | : + +- User payload -+ + : | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |0| + +-+ + Note that since we always merge adjacent free chunks, the chunks + adjacent to a free chunk must be in use. + + Given a pointer to a chunk (which can be derived trivially from the + payload pointer) we can, in O(1) time, find out whether the adjacent + chunks are free, and if so, unlink them from the lists that they + are on and merge them with the current chunk. + + Chunks always begin on even word boundaries, so the mem portion + (which is returned to the user) is also on an even word boundary, and + thus at least double-word aligned. + + The P (PINUSE_BIT) bit, stored in the unused low-order bit of the + chunk size (which is always a multiple of two words), is an in-use + bit for the *previous* chunk. If that bit is *clear*, then the + word before the current chunk size contains the previous chunk + size, and can be used to find the front of the previous chunk. + The very first chunk allocated always has this bit set, preventing + access to non-existent (or non-owned) memory. If pinuse is set for + any given chunk, then you CANNOT determine the size of the + previous chunk, and might even get a memory addressing fault when + trying to do so. + + The C (CINUSE_BIT) bit, stored in the unused second-lowest bit of + the chunk size redundantly records whether the current chunk is + inuse (unless the chunk is mmapped). This redundancy enables usage + checks within free and realloc, and reduces indirection when freeing + and consolidating chunks. + + Each freshly allocated chunk must have both cinuse and pinuse set. + That is, each allocated chunk borders either a previously allocated + and still in-use chunk, or the base of its memory arena. This is + ensured by making all allocations from the `lowest' part of any + found chunk. Further, no free chunk physically borders another one, + so each free chunk is known to be preceded and followed by either + inuse chunks or the ends of memory. + + Note that the `foot' of the current chunk is actually represented + as the prev_foot of the NEXT chunk. This makes it easier to + deal with alignments etc but can be very confusing when trying + to extend or adapt this code. + + The exceptions to all this are + + 1. The special chunk `top' is the top-most available chunk (i.e., + the one bordering the end of available memory). It is treated + specially. Top is never included in any bin, is used only if + no other chunk is available, and is released back to the + system if it is very large (see M_TRIM_THRESHOLD). In effect, + the top chunk is treated as larger (and thus less well + fitting) than any other available chunk. The top chunk + doesn't update its trailing size field since there is no next + contiguous chunk that would have to index off it. However, + space is still allocated for it (TOP_FOOT_SIZE) to enable + separation or merging when space is extended. + + 3. Chunks allocated via mmap, have both cinuse and pinuse bits + cleared in their head fields. Because they are allocated + one-by-one, each must carry its own prev_foot field, which is + also used to hold the offset this chunk has within its mmapped + region, which is needed to preserve alignment. Each mmapped + chunk is trailed by the first two fields of a fake next-chunk + for sake of usage checks. + +*/ + +struct malloc_chunk { + size_t prev_foot; /* Size of previous chunk (if free). */ + size_t head; /* Size and inuse bits. */ + struct malloc_chunk* fd; /* double links -- used only if free. */ + struct malloc_chunk* bk; +}; + +typedef struct malloc_chunk mchunk; +typedef struct malloc_chunk* mchunkptr; +typedef struct malloc_chunk* sbinptr; /* The type of bins of chunks */ +typedef unsigned int bindex_t; /* Described below */ +typedef unsigned int binmap_t; /* Described below */ +typedef unsigned int flag_t; /* The type of various bit flag sets */ + +/* ------------------- Chunks sizes and alignments ----------------------- */ + +#define MCHUNK_SIZE (sizeof(mchunk)) + +#if FOOTERS +#define CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) +#else /* FOOTERS */ +#define CHUNK_OVERHEAD (SIZE_T_SIZE) +#endif /* FOOTERS */ + +/* MMapped chunks need a second word of overhead ... */ +#define MMAP_CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) +/* ... and additional padding for fake next-chunk at foot */ +#define MMAP_FOOT_PAD (FOUR_SIZE_T_SIZES) + +/* The smallest size we can malloc is an aligned minimal chunk */ +#define MIN_CHUNK_SIZE\ + ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +/* conversion from malloc headers to user pointers, and back */ +#define chunk2mem(p) ((void*)((char*)(p) + TWO_SIZE_T_SIZES)) +#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES)) +/* chunk associated with aligned address A */ +#define align_as_chunk(A) (mchunkptr)((A) + align_offset(chunk2mem(A))) + +/* Bounds on request (not chunk) sizes. */ +#define MAX_REQUEST ((-MIN_CHUNK_SIZE) << 2) +#define MIN_REQUEST (MIN_CHUNK_SIZE - CHUNK_OVERHEAD - SIZE_T_ONE) + +/* pad request bytes into a usable size */ +#define pad_request(req) \ + (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +/* pad request, checking for minimum (but not maximum) */ +#define request2size(req) \ + (((req) < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(req)) + + +/* ------------------ Operations on head and foot fields ----------------- */ + +/* + The head field of a chunk is or'ed with PINUSE_BIT when previous + adjacent chunk in use, and or'ed with CINUSE_BIT if this chunk is in + use, unless mmapped, in which case both bits are cleared. + + FLAG4_BIT is not used by this malloc, but might be useful in extensions. +*/ + +#define PINUSE_BIT (SIZE_T_ONE) +#define CINUSE_BIT (SIZE_T_TWO) +#define FLAG4_BIT (SIZE_T_FOUR) +#define INUSE_BITS (PINUSE_BIT|CINUSE_BIT) +#define FLAG_BITS (PINUSE_BIT|CINUSE_BIT|FLAG4_BIT) + +/* Head value for fenceposts */ +#define FENCEPOST_HEAD (INUSE_BITS|SIZE_T_SIZE) + +/* extraction of fields from head words */ +#define cinuse(p) ((p)->head & CINUSE_BIT) +#define pinuse(p) ((p)->head & PINUSE_BIT) +#define flag4inuse(p) ((p)->head & FLAG4_BIT) +#define is_inuse(p) (((p)->head & INUSE_BITS) != PINUSE_BIT) +#define is_mmapped(p) (((p)->head & INUSE_BITS) == 0) + +#define chunksize(p) ((p)->head & ~(FLAG_BITS)) + +#define clear_pinuse(p) ((p)->head &= ~PINUSE_BIT) +#define set_flag4(p) ((p)->head |= FLAG4_BIT) +#define clear_flag4(p) ((p)->head &= ~FLAG4_BIT) + +/* Treat space at ptr +/- offset as a chunk */ +#define chunk_plus_offset(p, s) ((mchunkptr)(((char*)(p)) + (s))) +#define chunk_minus_offset(p, s) ((mchunkptr)(((char*)(p)) - (s))) + +/* Ptr to next or previous physical malloc_chunk. */ +#define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->head & ~FLAG_BITS))) +#define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_foot) )) + +/* extract next chunk's pinuse bit */ +#define next_pinuse(p) ((next_chunk(p)->head) & PINUSE_BIT) + +/* Get/set size at footer */ +#define get_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot) +#define set_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s)) + +/* Set size, pinuse bit, and foot */ +#define set_size_and_pinuse_of_free_chunk(p, s)\ + ((p)->head = (s|PINUSE_BIT), set_foot(p, s)) + +/* Set size, pinuse bit, foot, and clear next pinuse */ +#define set_free_with_pinuse(p, s, n)\ + (clear_pinuse(n), set_size_and_pinuse_of_free_chunk(p, s)) + +/* Get the internal overhead associated with chunk p */ +#define overhead_for(p)\ + (is_mmapped(p)? MMAP_CHUNK_OVERHEAD : CHUNK_OVERHEAD) + +/* Return true if malloced space is not necessarily cleared */ +#if MMAP_CLEARS +#define calloc_must_clear(p) (!is_mmapped(p)) +#else /* MMAP_CLEARS */ +#define calloc_must_clear(p) (1) +#endif /* MMAP_CLEARS */ + +/* ---------------------- Overlaid data structures ----------------------- */ + +/* + When chunks are not in use, they are treated as nodes of either + lists or trees. + + "Small" chunks are stored in circular doubly-linked lists, and look + like this: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `head:' | Size of chunk, in bytes |P| + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Forward pointer to next chunk in list | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Back pointer to previous chunk in list | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unused space (may be 0 bytes long) . + . . + . | +nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `foot:' | Size of chunk, in bytes | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Larger chunks are kept in a form of bitwise digital trees (aka + tries) keyed on chunksizes. Because malloc_tree_chunks are only for + free chunks greater than 256 bytes, their size doesn't impose any + constraints on user chunk sizes. Each node looks like: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `head:' | Size of chunk, in bytes |P| + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Forward pointer to next chunk of same size | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Back pointer to previous chunk of same size | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to left child (child[0]) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to right child (child[1]) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to parent | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | bin index of this chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unused space . + . | +nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `foot:' | Size of chunk, in bytes | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Each tree holding treenodes is a tree of unique chunk sizes. Chunks + of the same size are arranged in a circularly-linked list, with only + the oldest chunk (the next to be used, in our FIFO ordering) + actually in the tree. (Tree members are distinguished by a non-null + parent pointer.) If a chunk with the same size an an existing node + is inserted, it is linked off the existing node using pointers that + work in the same way as fd/bk pointers of small chunks. + + Each tree contains a power of 2 sized range of chunk sizes (the + smallest is 0x100 <= x < 0x180), which is is divided in half at each + tree level, with the chunks in the smaller half of the range (0x100 + <= x < 0x140 for the top nose) in the left subtree and the larger + half (0x140 <= x < 0x180) in the right subtree. This is, of course, + done by inspecting individual bits. + + Using these rules, each node's left subtree contains all smaller + sizes than its right subtree. However, the node at the root of each + subtree has no particular ordering relationship to either. (The + dividing line between the subtree sizes is based on trie relation.) + If we remove the last chunk of a given size from the interior of the + tree, we need to replace it with a leaf node. The tree ordering + rules permit a node to be replaced by any leaf below it. + + The smallest chunk in a tree (a common operation in a best-fit + allocator) can be found by walking a path to the leftmost leaf in + the tree. Unlike a usual binary tree, where we follow left child + pointers until we reach a null, here we follow the right child + pointer any time the left one is null, until we reach a leaf with + both child pointers null. The smallest chunk in the tree will be + somewhere along that path. + + The worst case number of steps to add, find, or remove a node is + bounded by the number of bits differentiating chunks within + bins. Under current bin calculations, this ranges from 6 up to 21 + (for 32 bit sizes) or up to 53 (for 64 bit sizes). The typical case + is of course much better. +*/ + +struct malloc_tree_chunk { + /* The first four fields must be compatible with malloc_chunk */ + size_t prev_foot; + size_t head; + struct malloc_tree_chunk* fd; + struct malloc_tree_chunk* bk; + + struct malloc_tree_chunk* child[2]; + struct malloc_tree_chunk* parent; + bindex_t index; +}; + +typedef struct malloc_tree_chunk tchunk; +typedef struct malloc_tree_chunk* tchunkptr; +typedef struct malloc_tree_chunk* tbinptr; /* The type of bins of trees */ + +/* A little helper macro for trees */ +#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0] : (t)->child[1]) + +/* ----------------------------- Segments -------------------------------- */ + +/* + Each malloc space may include non-contiguous segments, held in a + list headed by an embedded malloc_segment record representing the + top-most space. Segments also include flags holding properties of + the space. Large chunks that are directly allocated by mmap are not + included in this list. They are instead independently created and + destroyed without otherwise keeping track of them. + + Segment management mainly comes into play for spaces allocated by + MMAP. Any call to MMAP might or might not return memory that is + adjacent to an existing segment. MORECORE normally contiguously + extends the current space, so this space is almost always adjacent, + which is simpler and faster to deal with. (This is why MORECORE is + used preferentially to MMAP when both are available -- see + sys_alloc.) When allocating using MMAP, we don't use any of the + hinting mechanisms (inconsistently) supported in various + implementations of unix mmap, or distinguish reserving from + committing memory. Instead, we just ask for space, and exploit + contiguity when we get it. It is probably possible to do + better than this on some systems, but no general scheme seems + to be significantly better. + + Management entails a simpler variant of the consolidation scheme + used for chunks to reduce fragmentation -- new adjacent memory is + normally prepended or appended to an existing segment. However, + there are limitations compared to chunk consolidation that mostly + reflect the fact that segment processing is relatively infrequent + (occurring only when getting memory from system) and that we + don't expect to have huge numbers of segments: + + * Segments are not indexed, so traversal requires linear scans. (It + would be possible to index these, but is not worth the extra + overhead and complexity for most programs on most platforms.) + * New segments are only appended to old ones when holding top-most + memory; if they cannot be prepended to others, they are held in + different segments. + + Except for the top-most segment of an mstate, each segment record + is kept at the tail of its segment. Segments are added by pushing + segment records onto the list headed by &mstate.seg for the + containing mstate. + + Segment flags control allocation/merge/deallocation policies: + * If EXTERN_BIT set, then we did not allocate this segment, + and so should not try to deallocate or merge with others. + (This currently holds only for the initial segment passed + into create_mspace_with_base.) + * If USE_MMAP_BIT set, the segment may be merged with + other surrounding mmapped segments and trimmed/de-allocated + using munmap. + * If neither bit is set, then the segment was obtained using + MORECORE so can be merged with surrounding MORECORE'd segments + and deallocated/trimmed using MORECORE with negative arguments. +*/ + +struct malloc_segment { + char* base; /* base address */ + size_t size; /* allocated size */ + struct malloc_segment* next; /* ptr to next segment */ + flag_t sflags; /* mmap and extern flag */ +}; + +#define is_mmapped_segment(S) ((S)->sflags & USE_MMAP_BIT) +#define is_extern_segment(S) ((S)->sflags & EXTERN_BIT) + +typedef struct malloc_segment msegment; +typedef struct malloc_segment* msegmentptr; + +/* ---------------------------- malloc_state ----------------------------- */ + +/* + A malloc_state holds all of the bookkeeping for a space. + The main fields are: + + Top + The topmost chunk of the currently active segment. Its size is + cached in topsize. The actual size of topmost space is + topsize+TOP_FOOT_SIZE, which includes space reserved for adding + fenceposts and segment records if necessary when getting more + space from the system. The size at which to autotrim top is + cached from mparams in trim_check, except that it is disabled if + an autotrim fails. + + Designated victim (dv) + This is the preferred chunk for servicing small requests that + don't have exact fits. It is normally the chunk split off most + recently to service another small request. Its size is cached in + dvsize. The link fields of this chunk are not maintained since it + is not kept in a bin. + + SmallBins + An array of bin headers for free chunks. These bins hold chunks + with sizes less than MIN_LARGE_SIZE bytes. Each bin contains + chunks of all the same size, spaced 8 bytes apart. To simplify + use in double-linked lists, each bin header acts as a malloc_chunk + pointing to the real first node, if it exists (else pointing to + itself). This avoids special-casing for headers. But to avoid + waste, we allocate only the fd/bk pointers of bins, and then use + repositioning tricks to treat these as the fields of a chunk. + + TreeBins + Treebins are pointers to the roots of trees holding a range of + sizes. There are 2 equally spaced treebins for each power of two + from TREE_SHIFT to TREE_SHIFT+16. The last bin holds anything + larger. + + Bin maps + There is one bit map for small bins ("smallmap") and one for + treebins ("treemap). Each bin sets its bit when non-empty, and + clears the bit when empty. Bit operations are then used to avoid + bin-by-bin searching -- nearly all "search" is done without ever + looking at bins that won't be selected. The bit maps + conservatively use 32 bits per map word, even if on 64bit system. + For a good description of some of the bit-based techniques used + here, see Henry S. Warren Jr's book "Hacker's Delight" (and + supplement at http://hackersdelight.org/). Many of these are + intended to reduce the branchiness of paths through malloc etc, as + well as to reduce the number of memory locations read or written. + + Segments + A list of segments headed by an embedded malloc_segment record + representing the initial space. + + Address check support + The least_addr field is the least address ever obtained from + MORECORE or MMAP. Attempted frees and reallocs of any address less + than this are trapped (unless INSECURE is defined). + + Magic tag + A cross-check field that should always hold same value as mparams.magic. + + Max allowed footprint + The maximum allowed bytes to allocate from system (zero means no limit) + + Flags + Bits recording whether to use MMAP, locks, or contiguous MORECORE + + Statistics + Each space keeps track of current and maximum system memory + obtained via MORECORE or MMAP. + + Trim support + Fields holding the amount of unused topmost memory that should trigger + trimming, and a counter to force periodic scanning to release unused + non-topmost segments. + + Locking + If USE_LOCKS is defined, the "mutex" lock is acquired and released + around every public call using this mspace. + + Extension support + A void* pointer and a size_t field that can be used to help implement + extensions to this malloc. +*/ + +/* Bin types, widths and sizes */ +#define NSMALLBINS (32U) +#define NTREEBINS (32U) +#define SMALLBIN_SHIFT (3U) +#define SMALLBIN_WIDTH (SIZE_T_ONE << SMALLBIN_SHIFT) +#define TREEBIN_SHIFT (8U) +#define MIN_LARGE_SIZE (SIZE_T_ONE << TREEBIN_SHIFT) +#define MAX_SMALL_SIZE (MIN_LARGE_SIZE - SIZE_T_ONE) +#define MAX_SMALL_REQUEST (MAX_SMALL_SIZE - CHUNK_ALIGN_MASK - CHUNK_OVERHEAD) + +struct malloc_state { + binmap_t smallmap; + binmap_t treemap; + size_t dvsize; + size_t topsize; + char* least_addr; + mchunkptr dv; + mchunkptr top; + size_t trim_check; + size_t release_checks; + size_t magic; + mchunkptr smallbins[(NSMALLBINS+1)*2]; + tbinptr treebins[NTREEBINS]; + size_t footprint; + size_t max_footprint; + size_t footprint_limit; /* zero means no limit */ + flag_t mflags; +#if USE_LOCKS + MLOCK_T mutex; /* locate lock among fields that rarely change */ +#endif /* USE_LOCKS */ + msegment seg; + void* extp; /* Unused but available for extensions */ + size_t exts; +}; + +typedef struct malloc_state* mstate; + +/* ------------- Global malloc_state and malloc_params ------------------- */ + +/* + malloc_params holds global properties, including those that can be + dynamically set using mallopt. There is a single instance, mparams, + initialized in init_mparams. Note that the non-zeroness of "magic" + also serves as an initialization flag. +*/ + +struct malloc_params { + size_t magic; + size_t page_size; + size_t granularity; + size_t mmap_threshold; + size_t trim_threshold; + flag_t default_mflags; +}; + +static struct malloc_params mparams; + +/* Ensure mparams initialized */ +#define ensure_initialization() (void)(mparams.magic != 0 || init_mparams()) + +#if !ONLY_MSPACES + +/* The global malloc_state used for all non-"mspace" calls */ +static struct malloc_state _gm_; +#define gm (&_gm_) +#define is_global(M) ((M) == &_gm_) + +#endif /* !ONLY_MSPACES */ + +#define is_initialized(M) ((M)->top != 0) + +/* -------------------------- system alloc setup ------------------------- */ + +/* Operations on mflags */ + +#define use_lock(M) ((M)->mflags & USE_LOCK_BIT) +#define enable_lock(M) ((M)->mflags |= USE_LOCK_BIT) +#if USE_LOCKS +#define disable_lock(M) ((M)->mflags &= ~USE_LOCK_BIT) +#else +#define disable_lock(M) +#endif + +#define use_mmap(M) ((M)->mflags & USE_MMAP_BIT) +#define enable_mmap(M) ((M)->mflags |= USE_MMAP_BIT) +#if HAVE_MMAP +#define disable_mmap(M) ((M)->mflags &= ~USE_MMAP_BIT) +#else +#define disable_mmap(M) +#endif + +#define use_noncontiguous(M) ((M)->mflags & USE_NONCONTIGUOUS_BIT) +#define disable_contiguous(M) ((M)->mflags |= USE_NONCONTIGUOUS_BIT) + +#define set_lock(M,L)\ + ((M)->mflags = (L)?\ + ((M)->mflags | USE_LOCK_BIT) :\ + ((M)->mflags & ~USE_LOCK_BIT)) + +/* page-align a size */ +#define page_align(S)\ + (((S) + (mparams.page_size - SIZE_T_ONE)) & ~(mparams.page_size - SIZE_T_ONE)) + +/* granularity-align a size */ +#define granularity_align(S)\ + (((S) + (mparams.granularity - SIZE_T_ONE))\ + & ~(mparams.granularity - SIZE_T_ONE)) + + +/* For mmap, use granularity alignment on windows, else page-align */ +#ifdef WIN32 +#define mmap_align(S) granularity_align(S) +#else +#define mmap_align(S) page_align(S) +#endif + +/* For sys_alloc, enough padding to ensure can malloc request on success */ +#define SYS_ALLOC_PADDING (TOP_FOOT_SIZE + MALLOC_ALIGNMENT) + +#define is_page_aligned(S)\ + (((size_t)(S) & (mparams.page_size - SIZE_T_ONE)) == 0) +#define is_granularity_aligned(S)\ + (((size_t)(S) & (mparams.granularity - SIZE_T_ONE)) == 0) + +/* True if segment S holds address A */ +#define segment_holds(S, A)\ + ((char*)(A) >= S->base && (char*)(A) < S->base + S->size) + +/* Return segment holding given address */ +static msegmentptr segment_holding(mstate m, char* addr) { + msegmentptr sp = &m->seg; + for (;;) { + if (addr >= sp->base && addr < sp->base + sp->size) + return sp; + if ((sp = sp->next) == 0) + return 0; + } +} + +/* Return true if segment contains a segment link */ +static int has_segment_link(mstate m, msegmentptr ss) { + msegmentptr sp = &m->seg; + for (;;) { + if ((char*)sp >= ss->base && (char*)sp < ss->base + ss->size) + return 1; + if ((sp = sp->next) == 0) + return 0; + } +} + +#ifndef MORECORE_CANNOT_TRIM +#define should_trim(M,s) ((s) > (M)->trim_check) +#else /* MORECORE_CANNOT_TRIM */ +#define should_trim(M,s) (0) +#endif /* MORECORE_CANNOT_TRIM */ + +/* + TOP_FOOT_SIZE is padding at the end of a segment, including space + that may be needed to place segment records and fenceposts when new + noncontiguous segments are added. +*/ +#define TOP_FOOT_SIZE\ + (align_offset(chunk2mem(0))+pad_request(sizeof(struct malloc_segment))+MIN_CHUNK_SIZE) + + +/* ------------------------------- Hooks -------------------------------- */ + +/* + PREACTION should be defined to return 0 on success, and nonzero on + failure. If you are not using locking, you can redefine these to do + anything you like. +*/ + +#if USE_LOCKS +#define PREACTION(M) ((use_lock(M))? ACQUIRE_LOCK(&(M)->mutex) : 0) +#define POSTACTION(M) { if (use_lock(M)) RELEASE_LOCK(&(M)->mutex); } +#else /* USE_LOCKS */ + +#ifndef PREACTION +#define PREACTION(M) (0) +#endif /* PREACTION */ + +#ifndef POSTACTION +#define POSTACTION(M) +#endif /* POSTACTION */ + +#endif /* USE_LOCKS */ + +/* + CORRUPTION_ERROR_ACTION is triggered upon detected bad addresses. + USAGE_ERROR_ACTION is triggered on detected bad frees and + reallocs. The argument p is an address that might have triggered the + fault. It is ignored by the two predefined actions, but might be + useful in custom actions that try to help diagnose errors. +*/ + +#if PROCEED_ON_ERROR + +/* A count of the number of corruption errors causing resets */ +int malloc_corruption_error_count; + +/* default corruption action */ +static void reset_on_error(mstate m); + +#define CORRUPTION_ERROR_ACTION(m) reset_on_error(m) +#define USAGE_ERROR_ACTION(m, p) + +#else /* PROCEED_ON_ERROR */ + +#ifndef CORRUPTION_ERROR_ACTION +#define CORRUPTION_ERROR_ACTION(m) ABORT +#endif /* CORRUPTION_ERROR_ACTION */ + +#ifndef USAGE_ERROR_ACTION +#define USAGE_ERROR_ACTION(m,p) ABORT +#endif /* USAGE_ERROR_ACTION */ + +#endif /* PROCEED_ON_ERROR */ + + +/* -------------------------- Debugging setup ---------------------------- */ + +#if ! DEBUG + +#define check_free_chunk(M,P) +#define check_inuse_chunk(M,P) +#define check_malloced_chunk(M,P,N) +#define check_mmapped_chunk(M,P) +#define check_malloc_state(M) +#define check_top_chunk(M,P) + +#else /* DEBUG */ +#define check_free_chunk(M,P) do_check_free_chunk(M,P) +#define check_inuse_chunk(M,P) do_check_inuse_chunk(M,P) +#define check_top_chunk(M,P) do_check_top_chunk(M,P) +#define check_malloced_chunk(M,P,N) do_check_malloced_chunk(M,P,N) +#define check_mmapped_chunk(M,P) do_check_mmapped_chunk(M,P) +#define check_malloc_state(M) do_check_malloc_state(M) + +static void do_check_any_chunk(mstate m, mchunkptr p); +static void do_check_top_chunk(mstate m, mchunkptr p); +static void do_check_mmapped_chunk(mstate m, mchunkptr p); +static void do_check_inuse_chunk(mstate m, mchunkptr p); +static void do_check_free_chunk(mstate m, mchunkptr p); +static void do_check_malloced_chunk(mstate m, void* mem, size_t s); +static void do_check_tree(mstate m, tchunkptr t); +static void do_check_treebin(mstate m, bindex_t i); +static void do_check_smallbin(mstate m, bindex_t i); +static void do_check_malloc_state(mstate m); +static int bin_find(mstate m, mchunkptr x); +static size_t traverse_and_check(mstate m); +#endif /* DEBUG */ + +/* ---------------------------- Indexing Bins ---------------------------- */ + +#define is_small(s) (((s) >> SMALLBIN_SHIFT) < NSMALLBINS) +#define small_index(s) (bindex_t)((s) >> SMALLBIN_SHIFT) +#define small_index2size(i) ((i) << SMALLBIN_SHIFT) +#define MIN_SMALL_INDEX (small_index(MIN_CHUNK_SIZE)) + +/* addressing by index. See above about smallbin repositioning */ +#define smallbin_at(M, i) ((sbinptr)((char*)&((M)->smallbins[(i)<<1]))) +#define treebin_at(M,i) (&((M)->treebins[i])) + +/* assign tree index for size S to variable I. Use x86 asm if possible */ +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define compute_tree_index(S, I)\ +{\ + unsigned int X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K = (unsigned) sizeof(X)*__CHAR_BIT__ - 1 - (unsigned) __builtin_clz(X); \ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} + +#elif defined (__INTEL_COMPILER) +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K = _bit_scan_reverse (X); \ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} + +#elif defined(_MSC_VER) && _MSC_VER>=1300 +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K;\ + _BitScanReverse((DWORD *) &K, (DWORD) X);\ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} + +#else /* GNUC */ +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int Y = (unsigned int)X;\ + unsigned int N = ((Y - 0x100) >> 16) & 8;\ + unsigned int K = (((Y <<= N) - 0x1000) >> 16) & 4;\ + N += K;\ + N += K = (((Y <<= K) - 0x4000) >> 16) & 2;\ + K = 14 - N + ((Y <<= K) >> 15);\ + I = (K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1));\ + }\ +} +#endif /* GNUC */ + +/* Bit representing maximum resolved size in a treebin at i */ +#define bit_for_tree_index(i) \ + (i == NTREEBINS-1)? (SIZE_T_BITSIZE-1) : (((i) >> 1) + TREEBIN_SHIFT - 2) + +/* Shift placing maximum resolved bit in a treebin at i as sign bit */ +#define leftshift_for_tree_index(i) \ + ((i == NTREEBINS-1)? 0 : \ + ((SIZE_T_BITSIZE-SIZE_T_ONE) - (((i) >> 1) + TREEBIN_SHIFT - 2))) + +/* The size of the smallest chunk held in bin with index i */ +#define minsize_for_tree_index(i) \ + ((SIZE_T_ONE << (((i) >> 1) + TREEBIN_SHIFT)) | \ + (((size_t)((i) & SIZE_T_ONE)) << (((i) >> 1) + TREEBIN_SHIFT - 1))) + + +/* ------------------------ Operations on bin maps ----------------------- */ + +/* bit corresponding to given index */ +#define idx2bit(i) ((binmap_t)(1) << (i)) + +/* Mark/Clear bits with given index */ +#define mark_smallmap(M,i) ((M)->smallmap |= idx2bit(i)) +#define clear_smallmap(M,i) ((M)->smallmap &= ~idx2bit(i)) +#define smallmap_is_marked(M,i) ((M)->smallmap & idx2bit(i)) + +#define mark_treemap(M,i) ((M)->treemap |= idx2bit(i)) +#define clear_treemap(M,i) ((M)->treemap &= ~idx2bit(i)) +#define treemap_is_marked(M,i) ((M)->treemap & idx2bit(i)) + +/* isolate the least set bit of a bitmap */ +#define least_bit(x) ((x) & -(x)) + +/* mask with all bits to left of least bit of x on */ +#define left_bits(x) ((x<<1) | -(x<<1)) + +/* mask with all bits to left of or equal to least bit of x on */ +#define same_or_left_bits(x) ((x) | -(x)) + +/* index corresponding to given bit. Use x86 asm if possible */ + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + J = __builtin_ctz(X); \ + I = (bindex_t)J;\ +} + +#elif defined (__INTEL_COMPILER) +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + J = _bit_scan_forward (X); \ + I = (bindex_t)J;\ +} + +#elif defined(_MSC_VER) && _MSC_VER>=1300 +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + _BitScanForward((DWORD *) &J, X);\ + I = (bindex_t)J;\ +} + +#elif USE_BUILTIN_FFS +#define compute_bit2idx(X, I) I = ffs(X)-1 + +#else +#define compute_bit2idx(X, I)\ +{\ + unsigned int Y = X - 1;\ + unsigned int K = Y >> (16-4) & 16;\ + unsigned int N = K; Y >>= K;\ + N += K = Y >> (8-3) & 8; Y >>= K;\ + N += K = Y >> (4-2) & 4; Y >>= K;\ + N += K = Y >> (2-1) & 2; Y >>= K;\ + N += K = Y >> (1-0) & 1; Y >>= K;\ + I = (bindex_t)(N + Y);\ +} +#endif /* GNUC */ + + +/* ----------------------- Runtime Check Support ------------------------- */ + +/* + For security, the main invariant is that malloc/free/etc never + writes to a static address other than malloc_state, unless static + malloc_state itself has been corrupted, which cannot occur via + malloc (because of these checks). In essence this means that we + believe all pointers, sizes, maps etc held in malloc_state, but + check all of those linked or offsetted from other embedded data + structures. These checks are interspersed with main code in a way + that tends to minimize their run-time cost. + + When FOOTERS is defined, in addition to range checking, we also + verify footer fields of inuse chunks, which can be used guarantee + that the mstate controlling malloc/free is intact. This is a + streamlined version of the approach described by William Robertson + et al in "Run-time Detection of Heap-based Overflows" LISA'03 + http://www.usenix.org/events/lisa03/tech/robertson.html The footer + of an inuse chunk holds the xor of its mstate and a random seed, + that is checked upon calls to free() and realloc(). This is + (probabalistically) unguessable from outside the program, but can be + computed by any code successfully malloc'ing any chunk, so does not + itself provide protection against code that has already broken + security through some other means. Unlike Robertson et al, we + always dynamically check addresses of all offset chunks (previous, + next, etc). This turns out to be cheaper than relying on hashes. +*/ + +#if !INSECURE +/* Check if address a is at least as high as any from MORECORE or MMAP */ +#define ok_address(M, a) ((char*)(a) >= (M)->least_addr) +/* Check if address of next chunk n is higher than base chunk p */ +#define ok_next(p, n) ((char*)(p) < (char*)(n)) +/* Check if p has inuse status */ +#define ok_inuse(p) is_inuse(p) +/* Check if p has its pinuse bit on */ +#define ok_pinuse(p) pinuse(p) + +#else /* !INSECURE */ +#define ok_address(M, a) (1) +#define ok_next(b, n) (1) +#define ok_inuse(p) (1) +#define ok_pinuse(p) (1) +#endif /* !INSECURE */ + +#if (FOOTERS && !INSECURE) +/* Check if (alleged) mstate m has expected magic field */ +#define ok_magic(M) ((M)->magic == mparams.magic) +#else /* (FOOTERS && !INSECURE) */ +#define ok_magic(M) (1) +#endif /* (FOOTERS && !INSECURE) */ + +/* In gcc, use __builtin_expect to minimize impact of checks */ +#if !INSECURE +#if defined(__GNUC__) && __GNUC__ >= 3 +#define RTCHECK(e) __builtin_expect(e, 1) +#else /* GNUC */ +#define RTCHECK(e) (e) +#endif /* GNUC */ +#else /* !INSECURE */ +#define RTCHECK(e) (1) +#endif /* !INSECURE */ + +/* macros to set up inuse chunks with or without footers */ + +#if !FOOTERS + +#define mark_inuse_foot(M,p,s) + +/* Macros for setting head/foot of non-mmapped chunks */ + +/* Set cinuse bit and pinuse bit of next chunk */ +#define set_inuse(M,p,s)\ + ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ + ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) + +/* Set cinuse and pinuse of this chunk and pinuse of next chunk */ +#define set_inuse_and_pinuse(M,p,s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) + +/* Set size, cinuse and pinuse bit of this chunk */ +#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT)) + +#else /* FOOTERS */ + +/* Set foot of inuse chunk to be xor of mstate and seed */ +#define mark_inuse_foot(M,p,s)\ + (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic)) + +#define get_mstate_for(p)\ + ((mstate)(((mchunkptr)((char*)(p) +\ + (chunksize(p))))->prev_foot ^ mparams.magic)) + +#define set_inuse(M,p,s)\ + ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ + (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT), \ + mark_inuse_foot(M,p,s)) + +#define set_inuse_and_pinuse(M,p,s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT),\ + mark_inuse_foot(M,p,s)) + +#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + mark_inuse_foot(M, p, s)) + +#endif /* !FOOTERS */ + +/* ---------------------------- setting mparams -------------------------- */ + +#if LOCK_AT_FORK +static void pre_fork(void) { ACQUIRE_LOCK(&(gm)->mutex); } +static void post_fork_parent(void) { RELEASE_LOCK(&(gm)->mutex); } +static void post_fork_child(void) { INITIAL_LOCK(&(gm)->mutex); } +#endif /* LOCK_AT_FORK */ + +/* Initialize mparams */ +static int init_mparams(void) { +#ifdef NEED_GLOBAL_LOCK_INIT + if (malloc_global_mutex_status <= 0) + init_malloc_global_mutex(); +#endif + + ACQUIRE_MALLOC_GLOBAL_LOCK(); + if (mparams.magic == 0) { + size_t magic; + size_t psize; + size_t gsize; + +#ifndef WIN32 + psize = malloc_getpagesize; + gsize = ((DEFAULT_GRANULARITY != 0)? DEFAULT_GRANULARITY : psize); +#else /* WIN32 */ + { + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + psize = system_info.dwPageSize; + gsize = ((DEFAULT_GRANULARITY != 0)? + DEFAULT_GRANULARITY : system_info.dwAllocationGranularity); + } +#endif /* WIN32 */ + + /* Sanity-check configuration: + size_t must be unsigned and as wide as pointer type. + ints must be at least 4 bytes. + alignment must be at least 8. + Alignment, min chunk size, and page size must all be powers of 2. + */ + if ((sizeof(size_t) != sizeof(char*)) || + (MAX_SIZE_T < MIN_CHUNK_SIZE) || + (sizeof(int) < 4) || + (MALLOC_ALIGNMENT < (size_t)8U) || + ((MALLOC_ALIGNMENT & (MALLOC_ALIGNMENT-SIZE_T_ONE)) != 0) || + ((MCHUNK_SIZE & (MCHUNK_SIZE-SIZE_T_ONE)) != 0) || + ((gsize & (gsize-SIZE_T_ONE)) != 0) || + ((psize & (psize-SIZE_T_ONE)) != 0)) + ABORT; + mparams.granularity = gsize; + mparams.page_size = psize; + mparams.mmap_threshold = DEFAULT_MMAP_THRESHOLD; + mparams.trim_threshold = DEFAULT_TRIM_THRESHOLD; +#if MORECORE_CONTIGUOUS + mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT; +#else /* MORECORE_CONTIGUOUS */ + mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT|USE_NONCONTIGUOUS_BIT; +#endif /* MORECORE_CONTIGUOUS */ + +#if !ONLY_MSPACES + /* Set up lock for main malloc area */ + gm->mflags = mparams.default_mflags; + (void)INITIAL_LOCK(&gm->mutex); +#endif +#if LOCK_AT_FORK + pthread_atfork(&pre_fork, &post_fork_parent, &post_fork_child); +#endif + + { +#if USE_DEV_RANDOM + int fd; + unsigned char buf[sizeof(size_t)]; + /* Try to use /dev/urandom, else fall back on using time */ + if ((fd = open("/dev/urandom", O_RDONLY)) >= 0 && + read(fd, buf, sizeof(buf)) == sizeof(buf)) { + magic = *((size_t *) buf); + close(fd); + } + else +#endif /* USE_DEV_RANDOM */ +#ifdef WIN32 + magic = (size_t)(GetTickCount() ^ (size_t)0x55555555U); +#elif defined(LACKS_TIME_H) + magic = (size_t)&magic ^ (size_t)0x55555555U; +#else + magic = (size_t)(time(0) ^ (size_t)0x55555555U); +#endif + magic |= (size_t)8U; /* ensure nonzero */ + magic &= ~(size_t)7U; /* improve chances of fault for bad values */ + /* Until memory modes commonly available, use volatile-write */ + (*(volatile size_t *)(&(mparams.magic))) = magic; + } + } + + RELEASE_MALLOC_GLOBAL_LOCK(); + return 1; +} + +/* support for mallopt */ +static int change_mparam(int param_number, int value) { + size_t val; + ensure_initialization(); + val = (value == -1)? MAX_SIZE_T : (size_t)value; + switch(param_number) { + case M_TRIM_THRESHOLD: + mparams.trim_threshold = val; + return 1; + case M_GRANULARITY: + if (val >= mparams.page_size && ((val & (val-1)) == 0)) { + mparams.granularity = val; + return 1; + } + else + return 0; + case M_MMAP_THRESHOLD: + mparams.mmap_threshold = val; + return 1; + default: + return 0; + } +} + +#if DEBUG +/* ------------------------- Debugging Support --------------------------- */ + +/* Check properties of any chunk, whether free, inuse, mmapped etc */ +static void do_check_any_chunk(mstate m, mchunkptr p) { + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); +} + +/* Check properties of top chunk */ +static void do_check_top_chunk(mstate m, mchunkptr p) { + msegmentptr sp = segment_holding(m, (char*)p); + size_t sz = p->head & ~INUSE_BITS; /* third-lowest bit can be set! */ + assert(sp != 0); + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); + assert(sz == m->topsize); + assert(sz > 0); + assert(sz == ((sp->base + sp->size) - (char*)p) - TOP_FOOT_SIZE); + assert(pinuse(p)); + assert(!pinuse(chunk_plus_offset(p, sz))); +} + +/* Check properties of (inuse) mmapped chunks */ +static void do_check_mmapped_chunk(mstate m, mchunkptr p) { + size_t sz = chunksize(p); + size_t len = (sz + (p->prev_foot) + MMAP_FOOT_PAD); + assert(is_mmapped(p)); + assert(use_mmap(m)); + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); + assert(!is_small(sz)); + assert((len & (mparams.page_size-SIZE_T_ONE)) == 0); + assert(chunk_plus_offset(p, sz)->head == FENCEPOST_HEAD); + assert(chunk_plus_offset(p, sz+SIZE_T_SIZE)->head == 0); +} + +/* Check properties of inuse chunks */ +static void do_check_inuse_chunk(mstate m, mchunkptr p) { + do_check_any_chunk(m, p); + assert(is_inuse(p)); + assert(next_pinuse(p)); + /* If not pinuse and not mmapped, previous chunk has OK offset */ + assert(is_mmapped(p) || pinuse(p) || next_chunk(prev_chunk(p)) == p); + if (is_mmapped(p)) + do_check_mmapped_chunk(m, p); +} + +/* Check properties of free chunks */ +static void do_check_free_chunk(mstate m, mchunkptr p) { + size_t sz = chunksize(p); + mchunkptr next = chunk_plus_offset(p, sz); + do_check_any_chunk(m, p); + assert(!is_inuse(p)); + assert(!next_pinuse(p)); + assert (!is_mmapped(p)); + if (p != m->dv && p != m->top) { + if (sz >= MIN_CHUNK_SIZE) { + assert((sz & CHUNK_ALIGN_MASK) == 0); + assert(is_aligned(chunk2mem(p))); + assert(next->prev_foot == sz); + assert(pinuse(p)); + assert (next == m->top || is_inuse(next)); + assert(p->fd->bk == p); + assert(p->bk->fd == p); + } + else /* markers are always of size SIZE_T_SIZE */ + assert(sz == SIZE_T_SIZE); + } +} + +/* Check properties of malloced chunks at the point they are malloced */ +static void do_check_malloced_chunk(mstate m, void* mem, size_t s) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + size_t sz = p->head & ~INUSE_BITS; + do_check_inuse_chunk(m, p); + assert((sz & CHUNK_ALIGN_MASK) == 0); + assert(sz >= MIN_CHUNK_SIZE); + assert(sz >= s); + /* unless mmapped, size is less than MIN_CHUNK_SIZE more than request */ + assert(is_mmapped(p) || sz < (s + MIN_CHUNK_SIZE)); + } +} + +/* Check a tree and its subtrees. */ +static void do_check_tree(mstate m, tchunkptr t) { + tchunkptr head = 0; + tchunkptr u = t; + bindex_t tindex = t->index; + size_t tsize = chunksize(t); + bindex_t idx; + compute_tree_index(tsize, idx); + assert(tindex == idx); + assert(tsize >= MIN_LARGE_SIZE); + assert(tsize >= minsize_for_tree_index(idx)); + assert((idx == NTREEBINS-1) || (tsize < minsize_for_tree_index((idx+1)))); + + do { /* traverse through chain of same-sized nodes */ + do_check_any_chunk(m, ((mchunkptr)u)); + assert(u->index == tindex); + assert(chunksize(u) == tsize); + assert(!is_inuse(u)); + assert(!next_pinuse(u)); + assert(u->fd->bk == u); + assert(u->bk->fd == u); + if (u->parent == 0) { + assert(u->child[0] == 0); + assert(u->child[1] == 0); + } + else { + assert(head == 0); /* only one node on chain has parent */ + head = u; + assert(u->parent != u); + assert (u->parent->child[0] == u || + u->parent->child[1] == u || + *((tbinptr*)(u->parent)) == u); + if (u->child[0] != 0) { + assert(u->child[0]->parent == u); + assert(u->child[0] != u); + do_check_tree(m, u->child[0]); + } + if (u->child[1] != 0) { + assert(u->child[1]->parent == u); + assert(u->child[1] != u); + do_check_tree(m, u->child[1]); + } + if (u->child[0] != 0 && u->child[1] != 0) { + assert(chunksize(u->child[0]) < chunksize(u->child[1])); + } + } + u = u->fd; + } while (u != t); + assert(head != 0); +} + +/* Check all the chunks in a treebin. */ +static void do_check_treebin(mstate m, bindex_t i) { + tbinptr* tb = treebin_at(m, i); + tchunkptr t = *tb; + int empty = (m->treemap & (1U << i)) == 0; + if (t == 0) + assert(empty); + if (!empty) + do_check_tree(m, t); +} + +/* Check all the chunks in a smallbin. */ +static void do_check_smallbin(mstate m, bindex_t i) { + sbinptr b = smallbin_at(m, i); + mchunkptr p = b->bk; + unsigned int empty = (m->smallmap & (1U << i)) == 0; + if (p == b) + assert(empty); + if (!empty) { + for (; p != b; p = p->bk) { + size_t size = chunksize(p); + mchunkptr q; + /* each chunk claims to be free */ + do_check_free_chunk(m, p); + /* chunk belongs in bin */ + assert(small_index(size) == i); + assert(p->bk == b || chunksize(p->bk) == chunksize(p)); + /* chunk is followed by an inuse chunk */ + q = next_chunk(p); + if (q->head != FENCEPOST_HEAD) + do_check_inuse_chunk(m, q); + } + } +} + +/* Find x in a bin. Used in other check functions. */ +static int bin_find(mstate m, mchunkptr x) { + size_t size = chunksize(x); + if (is_small(size)) { + bindex_t sidx = small_index(size); + sbinptr b = smallbin_at(m, sidx); + if (smallmap_is_marked(m, sidx)) { + mchunkptr p = b; + do { + if (p == x) + return 1; + } while ((p = p->fd) != b); + } + } + else { + bindex_t tidx; + compute_tree_index(size, tidx); + if (treemap_is_marked(m, tidx)) { + tchunkptr t = *treebin_at(m, tidx); + size_t sizebits = size << leftshift_for_tree_index(tidx); + while (t != 0 && chunksize(t) != size) { + t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; + sizebits <<= 1; + } + if (t != 0) { + tchunkptr u = t; + do { + if (u == (tchunkptr)x) + return 1; + } while ((u = u->fd) != t); + } + } + } + return 0; +} + +/* Traverse each chunk and check it; return total */ +static size_t traverse_and_check(mstate m) { + size_t sum = 0; + if (is_initialized(m)) { + msegmentptr s = &m->seg; + sum += m->topsize + TOP_FOOT_SIZE; + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + mchunkptr lastq = 0; + assert(pinuse(q)); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + sum += chunksize(q); + if (is_inuse(q)) { + assert(!bin_find(m, q)); + do_check_inuse_chunk(m, q); + } + else { + assert(q == m->dv || bin_find(m, q)); + assert(lastq == 0 || is_inuse(lastq)); /* Not 2 consecutive free */ + do_check_free_chunk(m, q); + } + lastq = q; + q = next_chunk(q); + } + s = s->next; + } + } + return sum; +} + + +/* Check all properties of malloc_state. */ +static void do_check_malloc_state(mstate m) { + bindex_t i; + size_t total; + /* check bins */ + for (i = 0; i < NSMALLBINS; ++i) + do_check_smallbin(m, i); + for (i = 0; i < NTREEBINS; ++i) + do_check_treebin(m, i); + + if (m->dvsize != 0) { /* check dv chunk */ + do_check_any_chunk(m, m->dv); + assert(m->dvsize == chunksize(m->dv)); + assert(m->dvsize >= MIN_CHUNK_SIZE); + assert(bin_find(m, m->dv) == 0); + } + + if (m->top != 0) { /* check top chunk */ + do_check_top_chunk(m, m->top); + /*assert(m->topsize == chunksize(m->top)); redundant */ + assert(m->topsize > 0); + assert(bin_find(m, m->top) == 0); + } + + total = traverse_and_check(m); + assert(total <= m->footprint); + assert(m->footprint <= m->max_footprint); +} +#endif /* DEBUG */ + +/* ----------------------------- statistics ------------------------------ */ + +#if !NO_MALLINFO +static struct mallinfo internal_mallinfo(mstate m) { + struct mallinfo nm = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + ensure_initialization(); + if (!PREACTION(m)) { + check_malloc_state(m); + if (is_initialized(m)) { + size_t nfree = SIZE_T_ONE; /* top always free */ + size_t mfree = m->topsize + TOP_FOOT_SIZE; + size_t sum = mfree; + msegmentptr s = &m->seg; + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + size_t sz = chunksize(q); + sum += sz; + if (!is_inuse(q)) { + mfree += sz; + ++nfree; + } + q = next_chunk(q); + } + s = s->next; + } + + nm.arena = sum; + nm.ordblks = nfree; + nm.hblkhd = m->footprint - sum; + nm.usmblks = m->max_footprint; + nm.uordblks = m->footprint - mfree; + nm.fordblks = mfree; + nm.keepcost = m->topsize; + } + + POSTACTION(m); + } + return nm; +} +#endif /* !NO_MALLINFO */ + +#if !NO_MALLOC_STATS +static void internal_malloc_stats(mstate m) { + ensure_initialization(); + if (!PREACTION(m)) { + size_t maxfp = 0; + size_t fp = 0; + size_t used = 0; + check_malloc_state(m); + if (is_initialized(m)) { + msegmentptr s = &m->seg; + maxfp = m->max_footprint; + fp = m->footprint; + used = fp - (m->topsize + TOP_FOOT_SIZE); + + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + if (!is_inuse(q)) + used -= chunksize(q); + q = next_chunk(q); + } + s = s->next; + } + } + POSTACTION(m); /* drop lock */ + fprintf(stderr, "max system bytes = %10lu\n", (unsigned long)(maxfp)); + fprintf(stderr, "system bytes = %10lu\n", (unsigned long)(fp)); + fprintf(stderr, "in use bytes = %10lu\n", (unsigned long)(used)); + } +} +#endif /* NO_MALLOC_STATS */ + +/* ----------------------- Operations on smallbins ----------------------- */ + +/* + Various forms of linking and unlinking are defined as macros. Even + the ones for trees, which are very long but have very short typical + paths. This is ugly but reduces reliance on inlining support of + compilers. +*/ + +/* Link a free chunk into a smallbin */ +#define insert_small_chunk(M, P, S) {\ + bindex_t I = small_index(S);\ + mchunkptr B = smallbin_at(M, I);\ + mchunkptr F = B;\ + assert(S >= MIN_CHUNK_SIZE);\ + if (!smallmap_is_marked(M, I))\ + mark_smallmap(M, I);\ + else if (RTCHECK(ok_address(M, B->fd)))\ + F = B->fd;\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + B->fd = P;\ + F->bk = P;\ + P->fd = F;\ + P->bk = B;\ +} + +/* Unlink a chunk from a smallbin */ +#define unlink_small_chunk(M, P, S) {\ + mchunkptr F = P->fd;\ + mchunkptr B = P->bk;\ + bindex_t I = small_index(S);\ + assert(P != B);\ + assert(P != F);\ + assert(chunksize(P) == small_index2size(I));\ + if (RTCHECK(F == smallbin_at(M,I) || (ok_address(M, F) && F->bk == P))) { \ + if (B == F) {\ + clear_smallmap(M, I);\ + }\ + else if (RTCHECK(B == smallbin_at(M,I) ||\ + (ok_address(M, B) && B->fd == P))) {\ + F->bk = B;\ + B->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ +} + +/* Unlink the first chunk from a smallbin */ +#define unlink_first_small_chunk(M, B, P, I) {\ + mchunkptr F = P->fd;\ + assert(P != B);\ + assert(P != F);\ + assert(chunksize(P) == small_index2size(I));\ + if (B == F) {\ + clear_smallmap(M, I);\ + }\ + else if (RTCHECK(ok_address(M, F) && F->bk == P)) {\ + F->bk = B;\ + B->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ +} + +/* Replace dv node, binning the old one */ +/* Used only when dvsize known to be small */ +#define replace_dv(M, P, S) {\ + size_t DVS = M->dvsize;\ + assert(is_small(DVS));\ + if (DVS != 0) {\ + mchunkptr DV = M->dv;\ + insert_small_chunk(M, DV, DVS);\ + }\ + M->dvsize = S;\ + M->dv = P;\ +} + +/* ------------------------- Operations on trees ------------------------- */ + +/* Insert chunk into tree */ +#define insert_large_chunk(M, X, S) {\ + tbinptr* H;\ + bindex_t I;\ + compute_tree_index(S, I);\ + H = treebin_at(M, I);\ + X->index = I;\ + X->child[0] = X->child[1] = 0;\ + if (!treemap_is_marked(M, I)) {\ + mark_treemap(M, I);\ + *H = X;\ + X->parent = (tchunkptr)H;\ + X->fd = X->bk = X;\ + }\ + else {\ + tchunkptr T = *H;\ + size_t K = S << leftshift_for_tree_index(I);\ + for (;;) {\ + if (chunksize(T) != S) {\ + tchunkptr* C = &(T->child[(K >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]);\ + K <<= 1;\ + if (*C != 0)\ + T = *C;\ + else if (RTCHECK(ok_address(M, C))) {\ + *C = X;\ + X->parent = T;\ + X->fd = X->bk = X;\ + break;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + break;\ + }\ + }\ + else {\ + tchunkptr F = T->fd;\ + if (RTCHECK(ok_address(M, T) && ok_address(M, F))) {\ + T->fd = F->bk = X;\ + X->fd = F;\ + X->bk = T;\ + X->parent = 0;\ + break;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + break;\ + }\ + }\ + }\ + }\ +} + +/* + Unlink steps: + + 1. If x is a chained node, unlink it from its same-sized fd/bk links + and choose its bk node as its replacement. + 2. If x was the last node of its size, but not a leaf node, it must + be replaced with a leaf node (not merely one with an open left or + right), to make sure that lefts and rights of descendents + correspond properly to bit masks. We use the rightmost descendent + of x. We could use any other leaf, but this is easy to locate and + tends to counteract removal of leftmosts elsewhere, and so keeps + paths shorter than minimally guaranteed. This doesn't loop much + because on average a node in a tree is near the bottom. + 3. If x is the base of a chain (i.e., has parent links) relink + x's parent and children to x's replacement (or null if none). +*/ + +#define unlink_large_chunk(M, X) {\ + tchunkptr XP = X->parent;\ + tchunkptr R;\ + if (X->bk != X) {\ + tchunkptr F = X->fd;\ + R = X->bk;\ + if (RTCHECK(ok_address(M, F) && F->bk == X && R->fd == X)) {\ + F->bk = R;\ + R->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else {\ + tchunkptr* RP;\ + if (((R = *(RP = &(X->child[1]))) != 0) ||\ + ((R = *(RP = &(X->child[0]))) != 0)) {\ + tchunkptr* CP;\ + while ((*(CP = &(R->child[1])) != 0) ||\ + (*(CP = &(R->child[0])) != 0)) {\ + R = *(RP = CP);\ + }\ + if (RTCHECK(ok_address(M, RP)))\ + *RP = 0;\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + }\ + if (XP != 0) {\ + tbinptr* H = treebin_at(M, X->index);\ + if (X == *H) {\ + if ((*H = R) == 0) \ + clear_treemap(M, X->index);\ + }\ + else if (RTCHECK(ok_address(M, XP))) {\ + if (XP->child[0] == X) \ + XP->child[0] = R;\ + else \ + XP->child[1] = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + if (R != 0) {\ + if (RTCHECK(ok_address(M, R))) {\ + tchunkptr C0, C1;\ + R->parent = XP;\ + if ((C0 = X->child[0]) != 0) {\ + if (RTCHECK(ok_address(M, C0))) {\ + R->child[0] = C0;\ + C0->parent = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + if ((C1 = X->child[1]) != 0) {\ + if (RTCHECK(ok_address(M, C1))) {\ + R->child[1] = C1;\ + C1->parent = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ +} + +/* Relays to large vs small bin operations */ + +#define insert_chunk(M, P, S)\ + if (is_small(S)) insert_small_chunk(M, P, S)\ + else { tchunkptr TP = (tchunkptr)(P); insert_large_chunk(M, TP, S); } + +#define unlink_chunk(M, P, S)\ + if (is_small(S)) unlink_small_chunk(M, P, S)\ + else { tchunkptr TP = (tchunkptr)(P); unlink_large_chunk(M, TP); } + + +/* Relays to internal calls to malloc/free from realloc, memalign etc */ + +#if ONLY_MSPACES +#define internal_malloc(m, b) mspace_malloc(m, b) +#define internal_free(m, mem) mspace_free(m,mem); +#else /* ONLY_MSPACES */ +#if MSPACES +#define internal_malloc(m, b)\ + ((m == gm)? dlmalloc(b) : mspace_malloc(m, b)) +#define internal_free(m, mem)\ + if (m == gm) dlfree(mem); else mspace_free(m,mem); +#else /* MSPACES */ +#define internal_malloc(m, b) dlmalloc(b) +#define internal_free(m, mem) dlfree(mem) +#endif /* MSPACES */ +#endif /* ONLY_MSPACES */ + +/* ----------------------- Direct-mmapping chunks ----------------------- */ + +/* + Directly mmapped chunks are set up with an offset to the start of + the mmapped region stored in the prev_foot field of the chunk. This + allows reconstruction of the required argument to MUNMAP when freed, + and also allows adjustment of the returned chunk to meet alignment + requirements (especially in memalign). +*/ + +/* Malloc using mmap */ +static void* mmap_alloc(mstate m, size_t nb) { + size_t mmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + if (m->footprint_limit != 0) { + size_t fp = m->footprint + mmsize; + if (fp <= m->footprint || fp > m->footprint_limit) + return 0; + } + if (mmsize > nb) { /* Check for wrap around 0 */ + char* mm = (char*)(CALL_DIRECT_MMAP(mmsize)); + if (mm != CMFAIL) { + size_t offset = align_offset(chunk2mem(mm)); + size_t psize = mmsize - offset - MMAP_FOOT_PAD; + mchunkptr p = (mchunkptr)(mm + offset); + p->prev_foot = offset; + p->head = psize; + mark_inuse_foot(m, p, psize); + chunk_plus_offset(p, psize)->head = FENCEPOST_HEAD; + chunk_plus_offset(p, psize+SIZE_T_SIZE)->head = 0; + + if (m->least_addr == 0 || mm < m->least_addr) + m->least_addr = mm; + if ((m->footprint += mmsize) > m->max_footprint) + m->max_footprint = m->footprint; + assert(is_aligned(chunk2mem(p))); + check_mmapped_chunk(m, p); + return chunk2mem(p); + } + } + return 0; +} + +/* Realloc using mmap */ +static mchunkptr mmap_resize(mstate m, mchunkptr oldp, size_t nb, int flags) { + size_t oldsize = chunksize(oldp); + (void)flags; /* placate people compiling -Wunused */ + if (is_small(nb)) /* Can't shrink mmap regions below small size */ + return 0; + /* Keep old chunk if big enough but not too big */ + if (oldsize >= nb + SIZE_T_SIZE && + (oldsize - nb) <= (mparams.granularity << 1)) + return oldp; + else { + size_t offset = oldp->prev_foot; + size_t oldmmsize = oldsize + offset + MMAP_FOOT_PAD; + size_t newmmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + char* cp = (char*)CALL_MREMAP((char*)oldp - offset, + oldmmsize, newmmsize, flags); + if (cp != CMFAIL) { + mchunkptr newp = (mchunkptr)(cp + offset); + size_t psize = newmmsize - offset - MMAP_FOOT_PAD; + newp->head = psize; + mark_inuse_foot(m, newp, psize); + chunk_plus_offset(newp, psize)->head = FENCEPOST_HEAD; + chunk_plus_offset(newp, psize+SIZE_T_SIZE)->head = 0; + + if (cp < m->least_addr) + m->least_addr = cp; + if ((m->footprint += newmmsize - oldmmsize) > m->max_footprint) + m->max_footprint = m->footprint; + check_mmapped_chunk(m, newp); + return newp; + } + } + return 0; +} + + +/* -------------------------- mspace management -------------------------- */ + +/* Initialize top chunk and its size */ +static void init_top(mstate m, mchunkptr p, size_t psize) { + /* Ensure alignment */ + size_t offset = align_offset(chunk2mem(p)); + p = (mchunkptr)((char*)p + offset); + psize -= offset; + + m->top = p; + m->topsize = psize; + p->head = psize | PINUSE_BIT; + /* set size of fake trailing chunk holding overhead space only once */ + chunk_plus_offset(p, psize)->head = TOP_FOOT_SIZE; + m->trim_check = mparams.trim_threshold; /* reset on each update */ +} + +/* Initialize bins for a new mstate that is otherwise zeroed out */ +static void init_bins(mstate m) { + /* Establish circular links for smallbins */ + bindex_t i; + for (i = 0; i < NSMALLBINS; ++i) { + sbinptr bin = smallbin_at(m,i); + bin->fd = bin->bk = bin; + } +} + +#if PROCEED_ON_ERROR + +/* default corruption action */ +static void reset_on_error(mstate m) { + int i; + ++malloc_corruption_error_count; + /* Reinitialize fields to forget about all memory */ + m->smallmap = m->treemap = 0; + m->dvsize = m->topsize = 0; + m->seg.base = 0; + m->seg.size = 0; + m->seg.next = 0; + m->top = m->dv = 0; + for (i = 0; i < NTREEBINS; ++i) + *treebin_at(m, i) = 0; + init_bins(m); +} +#endif /* PROCEED_ON_ERROR */ + +/* Allocate chunk and prepend remainder with chunk in successor base. */ +static void* prepend_alloc(mstate m, char* newbase, char* oldbase, + size_t nb) { + mchunkptr p = align_as_chunk(newbase); + mchunkptr oldfirst = align_as_chunk(oldbase); + size_t psize = (char*)oldfirst - (char*)p; + mchunkptr q = chunk_plus_offset(p, nb); + size_t qsize = psize - nb; + set_size_and_pinuse_of_inuse_chunk(m, p, nb); + + assert((char*)oldfirst > (char*)q); + assert(pinuse(oldfirst)); + assert(qsize >= MIN_CHUNK_SIZE); + + /* consolidate remainder with first chunk of old base */ + if (oldfirst == m->top) { + size_t tsize = m->topsize += qsize; + m->top = q; + q->head = tsize | PINUSE_BIT; + check_top_chunk(m, q); + } + else if (oldfirst == m->dv) { + size_t dsize = m->dvsize += qsize; + m->dv = q; + set_size_and_pinuse_of_free_chunk(q, dsize); + } + else { + if (!is_inuse(oldfirst)) { + size_t nsize = chunksize(oldfirst); + unlink_chunk(m, oldfirst, nsize); + oldfirst = chunk_plus_offset(oldfirst, nsize); + qsize += nsize; + } + set_free_with_pinuse(q, qsize, oldfirst); + insert_chunk(m, q, qsize); + check_free_chunk(m, q); + } + + check_malloced_chunk(m, chunk2mem(p), nb); + return chunk2mem(p); +} + +/* Add a segment to hold a new noncontiguous region */ +static void add_segment(mstate m, char* tbase, size_t tsize, flag_t mmapped) { + /* Determine locations and sizes of segment, fenceposts, old top */ + char* old_top = (char*)m->top; + msegmentptr oldsp = segment_holding(m, old_top); + char* old_end = oldsp->base + oldsp->size; + size_t ssize = pad_request(sizeof(struct malloc_segment)); + char* rawsp = old_end - (ssize + FOUR_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + size_t offset = align_offset(chunk2mem(rawsp)); + char* asp = rawsp + offset; + char* csp = (asp < (old_top + MIN_CHUNK_SIZE))? old_top : asp; + mchunkptr sp = (mchunkptr)csp; + msegmentptr ss = (msegmentptr)(chunk2mem(sp)); + mchunkptr tnext = chunk_plus_offset(sp, ssize); + mchunkptr p = tnext; + int nfences = 0; + + /* reset top to new space */ + init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); + + /* Set up segment record */ + assert(is_aligned(ss)); + set_size_and_pinuse_of_inuse_chunk(m, sp, ssize); + *ss = m->seg; /* Push current record */ + m->seg.base = tbase; + m->seg.size = tsize; + m->seg.sflags = mmapped; + m->seg.next = ss; + + /* Insert trailing fenceposts */ + for (;;) { + mchunkptr nextp = chunk_plus_offset(p, SIZE_T_SIZE); + p->head = FENCEPOST_HEAD; + ++nfences; + if ((char*)(&(nextp->head)) < old_end) + p = nextp; + else + break; + } + assert(nfences >= 2); + + /* Insert the rest of old top into a bin as an ordinary free chunk */ + if (csp != old_top) { + mchunkptr q = (mchunkptr)old_top; + size_t psize = csp - old_top; + mchunkptr tn = chunk_plus_offset(q, psize); + set_free_with_pinuse(q, psize, tn); + insert_chunk(m, q, psize); + } + + check_top_chunk(m, m->top); +} + +/* -------------------------- System allocation -------------------------- */ + +/* Get memory from system using MORECORE or MMAP */ +static void* sys_alloc(mstate m, size_t nb) { + char* tbase = CMFAIL; + size_t tsize = 0; + flag_t mmap_flag = 0; + size_t asize; /* allocation size */ + + ensure_initialization(); + + /* Directly map large chunks, but only if already initialized */ + if (use_mmap(m) && nb >= mparams.mmap_threshold && m->topsize != 0) { + void* mem = mmap_alloc(m, nb); + if (mem != 0) + return mem; + } + + asize = granularity_align(nb + SYS_ALLOC_PADDING); + if (asize <= nb) + return 0; /* wraparound */ + if (m->footprint_limit != 0) { + size_t fp = m->footprint + asize; + if (fp <= m->footprint || fp > m->footprint_limit) + return 0; + } + + /* + Try getting memory in any of three ways (in most-preferred to + least-preferred order): + 1. A call to MORECORE that can normally contiguously extend memory. + (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or + or main space is mmapped or a previous contiguous call failed) + 2. A call to MMAP new space (disabled if not HAVE_MMAP). + Note that under the default settings, if MORECORE is unable to + fulfill a request, and HAVE_MMAP is true, then mmap is + used as a noncontiguous system allocator. This is a useful backup + strategy for systems with holes in address spaces -- in this case + sbrk cannot contiguously expand the heap, but mmap may be able to + find space. + 3. A call to MORECORE that cannot usually contiguously extend memory. + (disabled if not HAVE_MORECORE) + + In all cases, we need to request enough bytes from system to ensure + we can malloc nb bytes upon success, so pad with enough space for + top_foot, plus alignment-pad to make sure we don't lose bytes if + not on boundary, and round this up to a granularity unit. + */ + + if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) { + char* br = CMFAIL; + size_t ssize = asize; /* sbrk call size */ + msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top); + ACQUIRE_MALLOC_GLOBAL_LOCK(); + + if (ss == 0) { /* First time through or recovery */ + char* base = (char*)CALL_MORECORE(0); + if (base != CMFAIL) { + size_t fp; + /* Adjust to end on a page boundary */ + if (!is_page_aligned(base)) + ssize += (page_align((size_t)base) - (size_t)base); + fp = m->footprint + ssize; /* recheck limits */ + if (ssize > nb && ssize < HALF_MAX_SIZE_T && + (m->footprint_limit == 0 || + (fp > m->footprint && fp <= m->footprint_limit)) && + (br = (char*)(CALL_MORECORE(ssize))) == base) { + tbase = base; + tsize = ssize; + } + } + } + else { + /* Subtract out existing available top space from MORECORE request. */ + ssize = granularity_align(nb - m->topsize + SYS_ALLOC_PADDING); + /* Use mem here only if it did continuously extend old space */ + if (ssize < HALF_MAX_SIZE_T && + (br = (char*)(CALL_MORECORE(ssize))) == ss->base+ss->size) { + tbase = br; + tsize = ssize; + } + } + + if (tbase == CMFAIL) { /* Cope with partial failure */ + if (br != CMFAIL) { /* Try to use/extend the space we did get */ + if (ssize < HALF_MAX_SIZE_T && + ssize < nb + SYS_ALLOC_PADDING) { + size_t esize = granularity_align(nb + SYS_ALLOC_PADDING - ssize); + if (esize < HALF_MAX_SIZE_T) { + char* end = (char*)CALL_MORECORE(esize); + if (end != CMFAIL) + ssize += esize; + else { /* Can't use; try to release */ + (void) CALL_MORECORE(-ssize); + br = CMFAIL; + } + } + } + } + if (br != CMFAIL) { /* Use the space we did get */ + tbase = br; + tsize = ssize; + } + else + disable_contiguous(m); /* Don't try contiguous path in the future */ + } + + RELEASE_MALLOC_GLOBAL_LOCK(); + } + + if (HAVE_MMAP && tbase == CMFAIL) { /* Try MMAP */ + char* mp = (char*)(CALL_MMAP(asize)); + if (mp != CMFAIL) { + tbase = mp; + tsize = asize; + mmap_flag = USE_MMAP_BIT; + } + } + + if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */ + if (asize < HALF_MAX_SIZE_T) { + char* br = CMFAIL; + char* end = CMFAIL; + ACQUIRE_MALLOC_GLOBAL_LOCK(); + br = (char*)(CALL_MORECORE(asize)); + end = (char*)(CALL_MORECORE(0)); + RELEASE_MALLOC_GLOBAL_LOCK(); + if (br != CMFAIL && end != CMFAIL && br < end) { + size_t ssize = end - br; + if (ssize > nb + TOP_FOOT_SIZE) { + tbase = br; + tsize = ssize; + } + } + } + } + + if (tbase != CMFAIL) { + + if ((m->footprint += tsize) > m->max_footprint) + m->max_footprint = m->footprint; + + if (!is_initialized(m)) { /* first-time initialization */ + if (m->least_addr == 0 || tbase < m->least_addr) + m->least_addr = tbase; + m->seg.base = tbase; + m->seg.size = tsize; + m->seg.sflags = mmap_flag; + m->magic = mparams.magic; + m->release_checks = MAX_RELEASE_CHECK_RATE; + init_bins(m); +#if !ONLY_MSPACES + if (is_global(m)) + init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); + else +#endif + { + /* Offset top by embedded malloc_state */ + mchunkptr mn = next_chunk(mem2chunk(m)); + init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE); + } + } + + else { + /* Try to merge with an existing segment */ + msegmentptr sp = &m->seg; + /* Only consider most recent segment if traversal suppressed */ + while (sp != 0 && tbase != sp->base + sp->size) + sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next; + if (sp != 0 && + !is_extern_segment(sp) && + (sp->sflags & USE_MMAP_BIT) == mmap_flag && + segment_holds(sp, m->top)) { /* append */ + sp->size += tsize; + init_top(m, m->top, m->topsize + tsize); + } + else { + if (tbase < m->least_addr) + m->least_addr = tbase; + sp = &m->seg; + while (sp != 0 && sp->base != tbase + tsize) + sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next; + if (sp != 0 && + !is_extern_segment(sp) && + (sp->sflags & USE_MMAP_BIT) == mmap_flag) { + char* oldbase = sp->base; + sp->base = tbase; + sp->size += tsize; + return prepend_alloc(m, tbase, oldbase, nb); + } + else + add_segment(m, tbase, tsize, mmap_flag); + } + } + + if (nb < m->topsize) { /* Allocate from new or extended top space */ + size_t rsize = m->topsize -= nb; + mchunkptr p = m->top; + mchunkptr r = m->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(m, p, nb); + check_top_chunk(m, m->top); + check_malloced_chunk(m, chunk2mem(p), nb); + return chunk2mem(p); + } + } + + MALLOC_FAILURE_ACTION; + return 0; +} + +/* ----------------------- system deallocation -------------------------- */ + +/* Unmap and unlink any mmapped segments that don't contain used chunks */ +static size_t release_unused_segments(mstate m) { + size_t released = 0; + int nsegs = 0; + msegmentptr pred = &m->seg; + msegmentptr sp = pred->next; + while (sp != 0) { + char* base = sp->base; + size_t size = sp->size; + msegmentptr next = sp->next; + ++nsegs; + if (is_mmapped_segment(sp) && !is_extern_segment(sp)) { + mchunkptr p = align_as_chunk(base); + size_t psize = chunksize(p); + /* Can unmap if first chunk holds entire segment and not pinned */ + if (!is_inuse(p) && (char*)p + psize >= base + size - TOP_FOOT_SIZE) { + tchunkptr tp = (tchunkptr)p; + assert(segment_holds(sp, (char*)sp)); + if (p == m->dv) { + m->dv = 0; + m->dvsize = 0; + } + else { + unlink_large_chunk(m, tp); + } + if (CALL_MUNMAP(base, size) == 0) { + released += size; + m->footprint -= size; + /* unlink obsoleted record */ + sp = pred; + sp->next = next; + } + else { /* back out if cannot unmap */ + insert_large_chunk(m, tp, psize); + } + } + } + if (NO_SEGMENT_TRAVERSAL) /* scan only first segment */ + break; + pred = sp; + sp = next; + } + /* Reset check counter */ + m->release_checks = (((size_t) nsegs > (size_t) MAX_RELEASE_CHECK_RATE)? + (size_t) nsegs : (size_t) MAX_RELEASE_CHECK_RATE); + return released; +} + +static int sys_trim(mstate m, size_t pad) { + size_t released = 0; + ensure_initialization(); + if (pad < MAX_REQUEST && is_initialized(m)) { + pad += TOP_FOOT_SIZE; /* ensure enough room for segment overhead */ + + if (m->topsize > pad) { + /* Shrink top space in granularity-size units, keeping at least one */ + size_t unit = mparams.granularity; + size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit - + SIZE_T_ONE) * unit; + msegmentptr sp = segment_holding(m, (char*)m->top); + + if (!is_extern_segment(sp)) { + if (is_mmapped_segment(sp)) { + if (HAVE_MMAP && + sp->size >= extra && + !has_segment_link(m, sp)) { /* can't shrink if pinned */ + size_t newsize = sp->size - extra; + (void)newsize; /* placate people compiling -Wunused-variable */ + /* Prefer mremap, fall back to munmap */ + if ((CALL_MREMAP(sp->base, sp->size, newsize, 0) != MFAIL) || + (CALL_MUNMAP(sp->base + newsize, extra) == 0)) { + released = extra; + } + } + } + else if (HAVE_MORECORE) { + if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */ + extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit; + ACQUIRE_MALLOC_GLOBAL_LOCK(); + { + /* Make sure end of memory is where we last set it. */ + char* old_br = (char*)(CALL_MORECORE(0)); + if (old_br == sp->base + sp->size) { + char* rel_br = (char*)(CALL_MORECORE(-extra)); + char* new_br = (char*)(CALL_MORECORE(0)); + if (rel_br != CMFAIL && new_br < old_br) + released = old_br - new_br; + } + } + RELEASE_MALLOC_GLOBAL_LOCK(); + } + } + + if (released != 0) { + sp->size -= released; + m->footprint -= released; + init_top(m, m->top, m->topsize - released); + check_top_chunk(m, m->top); + } + } + + /* Unmap any unused mmapped segments */ + if (HAVE_MMAP) + released += release_unused_segments(m); + + /* On failure, disable autotrim to avoid repeated failed future calls */ + if (released == 0 && m->topsize > m->trim_check) + m->trim_check = MAX_SIZE_T; + } + + return (released != 0)? 1 : 0; +} + +/* Consolidate and bin a chunk. Differs from exported versions + of free mainly in that the chunk need not be marked as inuse. +*/ +static void dispose_chunk(mstate m, mchunkptr p, size_t psize) { + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + mchunkptr prev; + size_t prevsize = p->prev_foot; + if (is_mmapped(p)) { + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + m->footprint -= psize; + return; + } + prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(m, prev))) { /* consolidate backward */ + if (p != m->dv) { + unlink_chunk(m, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + m->dvsize = psize; + set_free_with_pinuse(p, psize, next); + return; + } + } + else { + CORRUPTION_ERROR_ACTION(m); + return; + } + } + if (RTCHECK(ok_address(m, next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == m->top) { + size_t tsize = m->topsize += psize; + m->top = p; + p->head = tsize | PINUSE_BIT; + if (p == m->dv) { + m->dv = 0; + m->dvsize = 0; + } + return; + } + else if (next == m->dv) { + size_t dsize = m->dvsize += psize; + m->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + return; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(m, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == m->dv) { + m->dvsize = psize; + return; + } + } + } + else { + set_free_with_pinuse(p, psize, next); + } + insert_chunk(m, p, psize); + } + else { + CORRUPTION_ERROR_ACTION(m); + } +} + +/* ---------------------------- malloc --------------------------- */ + +/* allocate a large request from the best fitting chunk in a treebin */ +static void* tmalloc_large(mstate m, size_t nb) { + tchunkptr v = 0; + size_t rsize = -nb; /* Unsigned negation */ + tchunkptr t; + bindex_t idx; + compute_tree_index(nb, idx); + if ((t = *treebin_at(m, idx)) != 0) { + /* Traverse tree for this bin looking for node with size == nb */ + size_t sizebits = nb << leftshift_for_tree_index(idx); + tchunkptr rst = 0; /* The deepest untaken right subtree */ + for (;;) { + tchunkptr rt; + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + v = t; + if ((rsize = trem) == 0) + break; + } + rt = t->child[1]; + t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; + if (rt != 0 && rt != t) + rst = rt; + if (t == 0) { + t = rst; /* set t to least subtree holding sizes > nb */ + break; + } + sizebits <<= 1; + } + } + if (t == 0 && v == 0) { /* set t to root of next non-empty treebin */ + binmap_t leftbits = left_bits(idx2bit(idx)) & m->treemap; + if (leftbits != 0) { + bindex_t i; + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + t = *treebin_at(m, i); + } + } + + while (t != 0) { /* find smallest of tree or subtree */ + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + rsize = trem; + v = t; + } + t = leftmost_child(t); + } + + /* If dv is a better fit, return 0 so malloc will use it */ + if (v != 0 && rsize < (size_t)(m->dvsize - nb)) { + if (RTCHECK(ok_address(m, v))) { /* split */ + mchunkptr r = chunk_plus_offset(v, nb); + assert(chunksize(v) == rsize + nb); + if (RTCHECK(ok_next(v, r))) { + unlink_large_chunk(m, v); + if (rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(m, v, (rsize + nb)); + else { + set_size_and_pinuse_of_inuse_chunk(m, v, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + insert_chunk(m, r, rsize); + } + return chunk2mem(v); + } + } + CORRUPTION_ERROR_ACTION(m); + } + return 0; +} + +/* allocate a small request from the best fitting chunk in a treebin */ +static void* tmalloc_small(mstate m, size_t nb) { + tchunkptr t, v; + size_t rsize; + bindex_t i; + binmap_t leastbit = least_bit(m->treemap); + compute_bit2idx(leastbit, i); + v = t = *treebin_at(m, i); + rsize = chunksize(t) - nb; + + while ((t = leftmost_child(t)) != 0) { + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + rsize = trem; + v = t; + } + } + + if (RTCHECK(ok_address(m, v))) { + mchunkptr r = chunk_plus_offset(v, nb); + assert(chunksize(v) == rsize + nb); + if (RTCHECK(ok_next(v, r))) { + unlink_large_chunk(m, v); + if (rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(m, v, (rsize + nb)); + else { + set_size_and_pinuse_of_inuse_chunk(m, v, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(m, r, rsize); + } + return chunk2mem(v); + } + } + + CORRUPTION_ERROR_ACTION(m); + return 0; +} + +#if !ONLY_MSPACES + +void* dlmalloc(size_t bytes) { + /* + Basic algorithm: + If a small request (< 256 bytes minus per-chunk overhead): + 1. If one exists, use a remainderless chunk in associated smallbin. + (Remainderless means that there are too few excess bytes to + represent as a chunk.) + 2. If it is big enough, use the dv chunk, which is normally the + chunk adjacent to the one used for the most recent small request. + 3. If one exists, split the smallest available chunk in a bin, + saving remainder in dv. + 4. If it is big enough, use the top chunk. + 5. If available, get memory from system and use it + Otherwise, for a large request: + 1. Find the smallest available binned chunk that fits, and use it + if it is better fitting than dv chunk, splitting if necessary. + 2. If better fitting than any binned chunk, use the dv chunk. + 3. If it is big enough, use the top chunk. + 4. If request size >= mmap threshold, try to directly mmap this chunk. + 5. If available, get memory from system and use it + + The ugly goto's here ensure that postaction occurs along all paths. + */ + +#if USE_LOCKS + ensure_initialization(); /* initialize in sys_alloc if not using locks */ +#endif + + if (!PREACTION(gm)) { + void* mem; + size_t nb; + if (bytes <= MAX_SMALL_REQUEST) { + bindex_t idx; + binmap_t smallbits; + nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); + idx = small_index(nb); + smallbits = gm->smallmap >> idx; + + if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ + mchunkptr b, p; + idx += ~smallbits & 1; /* Uses next bin if idx empty */ + b = smallbin_at(gm, idx); + p = b->fd; + assert(chunksize(p) == small_index2size(idx)); + unlink_first_small_chunk(gm, b, p, idx); + set_inuse_and_pinuse(gm, p, small_index2size(idx)); + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (nb > gm->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ + mchunkptr b, p, r; + size_t rsize; + bindex_t i; + binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + b = smallbin_at(gm, i); + p = b->fd; + assert(chunksize(p) == small_index2size(i)); + unlink_first_small_chunk(gm, b, p, i); + rsize = small_index2size(i) - nb; + /* Fit here cannot be remainderless if 4byte sizes */ + if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(gm, p, small_index2size(i)); + else { + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + r = chunk_plus_offset(p, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(gm, r, rsize); + } + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) { + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + } + } + else if (bytes >= MAX_REQUEST) + nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ + else { + nb = pad_request(bytes); + if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) { + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + } + + if (nb <= gm->dvsize) { + size_t rsize = gm->dvsize - nb; + mchunkptr p = gm->dv; + if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ + mchunkptr r = gm->dv = chunk_plus_offset(p, nb); + gm->dvsize = rsize; + set_size_and_pinuse_of_free_chunk(r, rsize); + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + } + else { /* exhaust dv */ + size_t dvs = gm->dvsize; + gm->dvsize = 0; + gm->dv = 0; + set_inuse_and_pinuse(gm, p, dvs); + } + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (nb < gm->topsize) { /* Split top */ + size_t rsize = gm->topsize -= nb; + mchunkptr p = gm->top; + mchunkptr r = gm->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + mem = chunk2mem(p); + check_top_chunk(gm, gm->top); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + mem = sys_alloc(gm, nb); + + postaction: + POSTACTION(gm); + return mem; + } + + return 0; +} + +/* ---------------------------- free --------------------------- */ + +void dlfree(void* mem) { + /* + Consolidate freed chunks with preceeding or succeeding bordering + free chunks, if they exist, and then place in a bin. Intermixed + with special cases for top, dv, mmapped chunks, and usage errors. + */ + + if (mem != 0) { + mchunkptr p = mem2chunk(mem); +#if FOOTERS + mstate fm = get_mstate_for(p); + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } +#else /* FOOTERS */ +#define fm gm +#endif /* FOOTERS */ + if (!PREACTION(fm)) { + check_inuse_chunk(fm, p); + if (RTCHECK(ok_address(fm, p) && ok_inuse(p))) { + size_t psize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + size_t prevsize = p->prev_foot; + if (is_mmapped(p)) { + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + fm->footprint -= psize; + goto postaction; + } + else { + mchunkptr prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ + if (p != fm->dv) { + unlink_chunk(fm, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + fm->dvsize = psize; + set_free_with_pinuse(p, psize, next); + goto postaction; + } + } + else + goto erroraction; + } + } + + if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == fm->top) { + size_t tsize = fm->topsize += psize; + fm->top = p; + p->head = tsize | PINUSE_BIT; + if (p == fm->dv) { + fm->dv = 0; + fm->dvsize = 0; + } + if (should_trim(fm, tsize)) + sys_trim(fm, 0); + goto postaction; + } + else if (next == fm->dv) { + size_t dsize = fm->dvsize += psize; + fm->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + goto postaction; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(fm, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == fm->dv) { + fm->dvsize = psize; + goto postaction; + } + } + } + else + set_free_with_pinuse(p, psize, next); + + if (is_small(psize)) { + insert_small_chunk(fm, p, psize); + check_free_chunk(fm, p); + } + else { + tchunkptr tp = (tchunkptr)p; + insert_large_chunk(fm, tp, psize); + check_free_chunk(fm, p); + if (--fm->release_checks == 0) + release_unused_segments(fm); + } + goto postaction; + } + } + erroraction: + USAGE_ERROR_ACTION(fm, p); + postaction: + POSTACTION(fm); + } + } +#if !FOOTERS +#undef fm +#endif /* FOOTERS */ +} + +void* dlcalloc(size_t n_elements, size_t elem_size) { + void* mem; + size_t req = 0; + if (n_elements != 0) { + req = n_elements * elem_size; + if (((n_elements | elem_size) & ~(size_t)0xffff) && + (req / n_elements != elem_size)) + req = MAX_SIZE_T; /* force downstream failure on overflow */ + } + mem = dlmalloc(req); + if (mem != 0 && calloc_must_clear(mem2chunk(mem))) + memset(mem, 0, req); + return mem; +} + +#endif /* !ONLY_MSPACES */ + +/* ------------ Internal support for realloc, memalign, etc -------------- */ + +/* Try to realloc; only in-place unless can_move true */ +static mchunkptr try_realloc_chunk(mstate m, mchunkptr p, size_t nb, + int can_move) { + mchunkptr newp = 0; + size_t oldsize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, oldsize); + if (RTCHECK(ok_address(m, p) && ok_inuse(p) && + ok_next(p, next) && ok_pinuse(next))) { + if (is_mmapped(p)) { + newp = mmap_resize(m, p, nb, can_move); + } + else if (oldsize >= nb) { /* already big enough */ + size_t rsize = oldsize - nb; + if (rsize >= MIN_CHUNK_SIZE) { /* split off remainder */ + mchunkptr r = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + set_inuse(m, r, rsize); + dispose_chunk(m, r, rsize); + } + newp = p; + } + else if (next == m->top) { /* extend into top */ + if (oldsize + m->topsize > nb) { + size_t newsize = oldsize + m->topsize; + size_t newtopsize = newsize - nb; + mchunkptr newtop = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + newtop->head = newtopsize |PINUSE_BIT; + m->top = newtop; + m->topsize = newtopsize; + newp = p; + } + } + else if (next == m->dv) { /* extend into dv */ + size_t dvs = m->dvsize; + if (oldsize + dvs >= nb) { + size_t dsize = oldsize + dvs - nb; + if (dsize >= MIN_CHUNK_SIZE) { + mchunkptr r = chunk_plus_offset(p, nb); + mchunkptr n = chunk_plus_offset(r, dsize); + set_inuse(m, p, nb); + set_size_and_pinuse_of_free_chunk(r, dsize); + clear_pinuse(n); + m->dvsize = dsize; + m->dv = r; + } + else { /* exhaust dv */ + size_t newsize = oldsize + dvs; + set_inuse(m, p, newsize); + m->dvsize = 0; + m->dv = 0; + } + newp = p; + } + } + else if (!cinuse(next)) { /* extend into next free chunk */ + size_t nextsize = chunksize(next); + if (oldsize + nextsize >= nb) { + size_t rsize = oldsize + nextsize - nb; + unlink_chunk(m, next, nextsize); + if (rsize < MIN_CHUNK_SIZE) { + size_t newsize = oldsize + nextsize; + set_inuse(m, p, newsize); + } + else { + mchunkptr r = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + set_inuse(m, r, rsize); + dispose_chunk(m, r, rsize); + } + newp = p; + } + } + } + else { + USAGE_ERROR_ACTION(m, chunk2mem(p)); + } + return newp; +} + +static void* internal_memalign(mstate m, size_t alignment, size_t bytes) { + void* mem = 0; + if (alignment < MIN_CHUNK_SIZE) /* must be at least a minimum chunk size */ + alignment = MIN_CHUNK_SIZE; + if ((alignment & (alignment-SIZE_T_ONE)) != 0) {/* Ensure a power of 2 */ + size_t a = MALLOC_ALIGNMENT << 1; + while (a < alignment) a <<= 1; + alignment = a; + } + if (bytes >= MAX_REQUEST - alignment) { + if (m != 0) { /* Test isn't needed but avoids compiler warning */ + MALLOC_FAILURE_ACTION; + } + } + else { + size_t nb = request2size(bytes); + size_t req = nb + alignment + MIN_CHUNK_SIZE - CHUNK_OVERHEAD; + mem = internal_malloc(m, req); + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (PREACTION(m)) + return 0; + if ((((size_t)(mem)) & (alignment - 1)) != 0) { /* misaligned */ + /* + Find an aligned spot inside chunk. Since we need to give + back leading space in a chunk of at least MIN_CHUNK_SIZE, if + the first calculation places us at a spot with less than + MIN_CHUNK_SIZE leader, we can move to the next aligned spot. + We've allocated enough total room so that this is always + possible. + */ + char* br = (char*)mem2chunk((size_t)(((size_t)((char*)mem + alignment - + SIZE_T_ONE)) & + -alignment)); + char* pos = ((size_t)(br - (char*)(p)) >= MIN_CHUNK_SIZE)? + br : br+alignment; + mchunkptr newp = (mchunkptr)pos; + size_t leadsize = pos - (char*)(p); + size_t newsize = chunksize(p) - leadsize; + + if (is_mmapped(p)) { /* For mmapped chunks, just adjust offset */ + newp->prev_foot = p->prev_foot + leadsize; + newp->head = newsize; + } + else { /* Otherwise, give back leader, use the rest */ + set_inuse(m, newp, newsize); + set_inuse(m, p, leadsize); + dispose_chunk(m, p, leadsize); + } + p = newp; + } + + /* Give back spare room at the end */ + if (!is_mmapped(p)) { + size_t size = chunksize(p); + if (size > nb + MIN_CHUNK_SIZE) { + size_t remainder_size = size - nb; + mchunkptr remainder = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + set_inuse(m, remainder, remainder_size); + dispose_chunk(m, remainder, remainder_size); + } + } + + mem = chunk2mem(p); + assert (chunksize(p) >= nb); + assert(((size_t)mem & (alignment - 1)) == 0); + check_inuse_chunk(m, p); + POSTACTION(m); + } + } + return mem; +} + +/* + Common support for independent_X routines, handling + all of the combinations that can result. + The opts arg has: + bit 0 set if all elements are same size (using sizes[0]) + bit 1 set if elements should be zeroed +*/ +static void** ialloc(mstate m, + size_t n_elements, + size_t* sizes, + int opts, + void* chunks[]) { + + size_t element_size; /* chunksize of each element, if all same */ + size_t contents_size; /* total size of elements */ + size_t array_size; /* request size of pointer array */ + void* mem; /* malloced aggregate space */ + mchunkptr p; /* corresponding chunk */ + size_t remainder_size; /* remaining bytes while splitting */ + void** marray; /* either "chunks" or malloced ptr array */ + mchunkptr array_chunk; /* chunk for malloced ptr array */ + flag_t was_enabled; /* to disable mmap */ + size_t size; + size_t i; + + ensure_initialization(); + /* compute array length, if needed */ + if (chunks != 0) { + if (n_elements == 0) + return chunks; /* nothing to do */ + marray = chunks; + array_size = 0; + } + else { + /* if empty req, must still return chunk representing empty array */ + if (n_elements == 0) + return (void**)internal_malloc(m, 0); + marray = 0; + array_size = request2size(n_elements * (sizeof(void*))); + } + + /* compute total element size */ + if (opts & 0x1) { /* all-same-size */ + element_size = request2size(*sizes); + contents_size = n_elements * element_size; + } + else { /* add up all the sizes */ + element_size = 0; + contents_size = 0; + for (i = 0; i != n_elements; ++i) + contents_size += request2size(sizes[i]); + } + + size = contents_size + array_size; + + /* + Allocate the aggregate chunk. First disable direct-mmapping so + malloc won't use it, since we would not be able to later + free/realloc space internal to a segregated mmap region. + */ + was_enabled = use_mmap(m); + disable_mmap(m); + mem = internal_malloc(m, size - CHUNK_OVERHEAD); + if (was_enabled) + enable_mmap(m); + if (mem == 0) + return 0; + + if (PREACTION(m)) return 0; + p = mem2chunk(mem); + remainder_size = chunksize(p); + + assert(!is_mmapped(p)); + + if (opts & 0x2) { /* optionally clear the elements */ + memset((size_t*)mem, 0, remainder_size - SIZE_T_SIZE - array_size); + } + + /* If not provided, allocate the pointer array as final part of chunk */ + if (marray == 0) { + size_t array_chunk_size; + array_chunk = chunk_plus_offset(p, contents_size); + array_chunk_size = remainder_size - contents_size; + marray = (void**) (chunk2mem(array_chunk)); + set_size_and_pinuse_of_inuse_chunk(m, array_chunk, array_chunk_size); + remainder_size = contents_size; + } + + /* split out elements */ + for (i = 0; ; ++i) { + marray[i] = chunk2mem(p); + if (i != n_elements-1) { + if (element_size != 0) + size = element_size; + else + size = request2size(sizes[i]); + remainder_size -= size; + set_size_and_pinuse_of_inuse_chunk(m, p, size); + p = chunk_plus_offset(p, size); + } + else { /* the final element absorbs any overallocation slop */ + set_size_and_pinuse_of_inuse_chunk(m, p, remainder_size); + break; + } + } + +#if DEBUG + if (marray != chunks) { + /* final element must have exactly exhausted chunk */ + if (element_size != 0) { + assert(remainder_size == element_size); + } + else { + assert(remainder_size == request2size(sizes[i])); + } + check_inuse_chunk(m, mem2chunk(marray)); + } + for (i = 0; i != n_elements; ++i) + check_inuse_chunk(m, mem2chunk(marray[i])); + +#endif /* DEBUG */ + + POSTACTION(m); + return marray; +} + +/* Try to free all pointers in the given array. + Note: this could be made faster, by delaying consolidation, + at the price of disabling some user integrity checks, We + still optimize some consolidations by combining adjacent + chunks before freeing, which will occur often if allocated + with ialloc or the array is sorted. +*/ +static size_t internal_bulk_free(mstate m, void* array[], size_t nelem) { + size_t unfreed = 0; + if (!PREACTION(m)) { + void** a; + void** fence = &(array[nelem]); + for (a = array; a != fence; ++a) { + void* mem = *a; + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + size_t psize = chunksize(p); +#if FOOTERS + if (get_mstate_for(p) != m) { + ++unfreed; + continue; + } +#endif + check_inuse_chunk(m, p); + *a = 0; + if (RTCHECK(ok_address(m, p) && ok_inuse(p))) { + void ** b = a + 1; /* try to merge with next chunk */ + mchunkptr next = next_chunk(p); + if (b != fence && *b == chunk2mem(next)) { + size_t newsize = chunksize(next) + psize; + set_inuse(m, p, newsize); + *b = chunk2mem(p); + } + else + dispose_chunk(m, p, psize); + } + else { + CORRUPTION_ERROR_ACTION(m); + break; + } + } + } + if (should_trim(m, m->topsize)) + sys_trim(m, 0); + POSTACTION(m); + } + return unfreed; +} + +/* Traversal */ +#if MALLOC_INSPECT_ALL +static void internal_inspect_all(mstate m, + void(*handler)(void *start, + void *end, + size_t used_bytes, + void* callback_arg), + void* arg) { + if (is_initialized(m)) { + mchunkptr top = m->top; + msegmentptr s; + for (s = &m->seg; s != 0; s = s->next) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && q->head != FENCEPOST_HEAD) { + mchunkptr next = next_chunk(q); + size_t sz = chunksize(q); + size_t used; + void* start; + if (is_inuse(q)) { + used = sz - CHUNK_OVERHEAD; /* must not be mmapped */ + start = chunk2mem(q); + } + else { + used = 0; + if (is_small(sz)) { /* offset by possible bookkeeping */ + start = (void*)((char*)q + sizeof(struct malloc_chunk)); + } + else { + start = (void*)((char*)q + sizeof(struct malloc_tree_chunk)); + } + } + if (start < (void*)next) /* skip if all space is bookkeeping */ + handler(start, next, used, arg); + if (q == top) + break; + q = next; + } + } + } +} +#endif /* MALLOC_INSPECT_ALL */ + +/* ------------------ Exported realloc, memalign, etc -------------------- */ + +#if !ONLY_MSPACES + +void* dlrealloc(void* oldmem, size_t bytes) { + void* mem = 0; + if (oldmem == 0) { + mem = dlmalloc(bytes); + } + else if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + } +#ifdef REALLOC_ZERO_BYTES_FREES + else if (bytes == 0) { + dlfree(oldmem); + } +#endif /* REALLOC_ZERO_BYTES_FREES */ + else { + size_t nb = request2size(bytes); + mchunkptr oldp = mem2chunk(oldmem); +#if ! FOOTERS + mstate m = gm; +#else /* FOOTERS */ + mstate m = get_mstate_for(oldp); + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + if (!PREACTION(m)) { + mchunkptr newp = try_realloc_chunk(m, oldp, nb, 1); + POSTACTION(m); + if (newp != 0) { + check_inuse_chunk(m, newp); + mem = chunk2mem(newp); + } + else { + mem = internal_malloc(m, bytes); + if (mem != 0) { + size_t oc = chunksize(oldp) - overhead_for(oldp); + memcpy(mem, oldmem, (oc < bytes)? oc : bytes); + internal_free(m, oldmem); + } + } + } + } + return mem; +} + +void* dlrealloc_in_place(void* oldmem, size_t bytes) { + void* mem = 0; + if (oldmem != 0) { + if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + } + else { + size_t nb = request2size(bytes); + mchunkptr oldp = mem2chunk(oldmem); +#if ! FOOTERS + mstate m = gm; +#else /* FOOTERS */ + mstate m = get_mstate_for(oldp); + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + if (!PREACTION(m)) { + mchunkptr newp = try_realloc_chunk(m, oldp, nb, 0); + POSTACTION(m); + if (newp == oldp) { + check_inuse_chunk(m, newp); + mem = oldmem; + } + } + } + } + return mem; +} + +void* dlmemalign(size_t alignment, size_t bytes) { + if (alignment <= MALLOC_ALIGNMENT) { + return dlmalloc(bytes); + } + return internal_memalign(gm, alignment, bytes); +} + +int dlposix_memalign(void** pp, size_t alignment, size_t bytes) { + void* mem = 0; + if (alignment == MALLOC_ALIGNMENT) + mem = dlmalloc(bytes); + else { + size_t d = alignment / sizeof(void*); + size_t r = alignment % sizeof(void*); + if (r != 0 || d == 0 || (d & (d-SIZE_T_ONE)) != 0) + return EINVAL; + else if (bytes <= MAX_REQUEST - alignment) { + if (alignment < MIN_CHUNK_SIZE) + alignment = MIN_CHUNK_SIZE; + mem = internal_memalign(gm, alignment, bytes); + } + } + if (mem == 0) + return ENOMEM; + else { + *pp = mem; + return 0; + } +} + +void* dlvalloc(size_t bytes) { + size_t pagesz; + ensure_initialization(); + pagesz = mparams.page_size; + return dlmemalign(pagesz, bytes); +} + +void* dlpvalloc(size_t bytes) { + size_t pagesz; + ensure_initialization(); + pagesz = mparams.page_size; + return dlmemalign(pagesz, (bytes + pagesz - SIZE_T_ONE) & ~(pagesz - SIZE_T_ONE)); +} + +void** dlindependent_calloc(size_t n_elements, size_t elem_size, + void* chunks[]) { + size_t sz = elem_size; /* serves as 1-element array */ + return ialloc(gm, n_elements, &sz, 3, chunks); +} + +void** dlindependent_comalloc(size_t n_elements, size_t sizes[], + void* chunks[]) { + return ialloc(gm, n_elements, sizes, 0, chunks); +} + +size_t dlbulk_free(void* array[], size_t nelem) { + return internal_bulk_free(gm, array, nelem); +} + +#if MALLOC_INSPECT_ALL +void dlmalloc_inspect_all(void(*handler)(void *start, + void *end, + size_t used_bytes, + void* callback_arg), + void* arg) { + ensure_initialization(); + if (!PREACTION(gm)) { + internal_inspect_all(gm, handler, arg); + POSTACTION(gm); + } +} +#endif /* MALLOC_INSPECT_ALL */ + +int dlmalloc_trim(size_t pad) { + int result = 0; + ensure_initialization(); + if (!PREACTION(gm)) { + result = sys_trim(gm, pad); + POSTACTION(gm); + } + return result; +} + +size_t dlmalloc_footprint(void) { + return gm->footprint; +} + +size_t dlmalloc_max_footprint(void) { + return gm->max_footprint; +} + +size_t dlmalloc_footprint_limit(void) { + size_t maf = gm->footprint_limit; + return maf == 0 ? MAX_SIZE_T : maf; +} + +size_t dlmalloc_set_footprint_limit(size_t bytes) { + size_t result; /* invert sense of 0 */ + if (bytes == 0) + result = granularity_align(1); /* Use minimal size */ + if (bytes == MAX_SIZE_T) + result = 0; /* disable */ + else + result = granularity_align(bytes); + return gm->footprint_limit = result; +} + +#if !NO_MALLINFO +struct mallinfo dlmallinfo(void) { + return internal_mallinfo(gm); +} +#endif /* NO_MALLINFO */ + +#if !NO_MALLOC_STATS +void dlmalloc_stats() { + internal_malloc_stats(gm); +} +#endif /* NO_MALLOC_STATS */ + +int dlmallopt(int param_number, int value) { + return change_mparam(param_number, value); +} + +size_t dlmalloc_usable_size(void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (is_inuse(p)) + return chunksize(p) - overhead_for(p); + } + return 0; +} + +#endif /* !ONLY_MSPACES */ + +/* ----------------------------- user mspaces ---------------------------- */ + +#if MSPACES + +static mstate init_user_mstate(char* tbase, size_t tsize) { + size_t msize = pad_request(sizeof(struct malloc_state)); + mchunkptr mn; + mchunkptr msp = align_as_chunk(tbase); + mstate m = (mstate)(chunk2mem(msp)); + memset(m, 0, msize); + (void)INITIAL_LOCK(&m->mutex); + msp->head = (msize|INUSE_BITS); + m->seg.base = m->least_addr = tbase; + m->seg.size = m->footprint = m->max_footprint = tsize; + m->magic = mparams.magic; + m->release_checks = MAX_RELEASE_CHECK_RATE; + m->mflags = mparams.default_mflags; + m->extp = 0; + m->exts = 0; + disable_contiguous(m); + init_bins(m); + mn = next_chunk(mem2chunk(m)); + init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) - TOP_FOOT_SIZE); + check_top_chunk(m, m->top); + return m; +} + +mspace create_mspace(size_t capacity, int locked) { + mstate m = 0; + size_t msize; + ensure_initialization(); + msize = pad_request(sizeof(struct malloc_state)); + if (capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { + size_t rs = ((capacity == 0)? mparams.granularity : + (capacity + TOP_FOOT_SIZE + msize)); + size_t tsize = granularity_align(rs); + char* tbase = (char*)(CALL_MMAP(tsize)); + if (tbase != CMFAIL) { + m = init_user_mstate(tbase, tsize); + m->seg.sflags = USE_MMAP_BIT; + set_lock(m, locked); + } + } + return (mspace)m; +} + +mspace create_mspace_with_base(void* base, size_t capacity, int locked) { + mstate m = 0; + size_t msize; + ensure_initialization(); + msize = pad_request(sizeof(struct malloc_state)); + if (capacity > msize + TOP_FOOT_SIZE && + capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { + m = init_user_mstate((char*)base, capacity); + m->seg.sflags = EXTERN_BIT; + set_lock(m, locked); + } + return (mspace)m; +} + +int mspace_track_large_chunks(mspace msp, int enable) { + int ret = 0; + mstate ms = (mstate)msp; + if (!PREACTION(ms)) { + if (!use_mmap(ms)) { + ret = 1; + } + if (!enable) { + enable_mmap(ms); + } else { + disable_mmap(ms); + } + POSTACTION(ms); + } + return ret; +} + +size_t destroy_mspace(mspace msp) { + size_t freed = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + msegmentptr sp = &ms->seg; + (void)DESTROY_LOCK(&ms->mutex); /* destroy before unmapped */ + while (sp != 0) { + char* base = sp->base; + size_t size = sp->size; + flag_t flag = sp->sflags; + (void)base; /* placate people compiling -Wunused-variable */ + sp = sp->next; + if ((flag & USE_MMAP_BIT) && !(flag & EXTERN_BIT) && + CALL_MUNMAP(base, size) == 0) + freed += size; + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return freed; +} + +/* + mspace versions of routines are near-clones of the global + versions. This is not so nice but better than the alternatives. +*/ + +void* mspace_malloc(mspace msp, size_t bytes) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (!PREACTION(ms)) { + void* mem; + size_t nb; + if (bytes <= MAX_SMALL_REQUEST) { + bindex_t idx; + binmap_t smallbits; + nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); + idx = small_index(nb); + smallbits = ms->smallmap >> idx; + + if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ + mchunkptr b, p; + idx += ~smallbits & 1; /* Uses next bin if idx empty */ + b = smallbin_at(ms, idx); + p = b->fd; + assert(chunksize(p) == small_index2size(idx)); + unlink_first_small_chunk(ms, b, p, idx); + set_inuse_and_pinuse(ms, p, small_index2size(idx)); + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (nb > ms->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ + mchunkptr b, p, r; + size_t rsize; + bindex_t i; + binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + b = smallbin_at(ms, i); + p = b->fd; + assert(chunksize(p) == small_index2size(i)); + unlink_first_small_chunk(ms, b, p, i); + rsize = small_index2size(i) - nb; + /* Fit here cannot be remainderless if 4byte sizes */ + if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(ms, p, small_index2size(i)); + else { + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + r = chunk_plus_offset(p, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(ms, r, rsize); + } + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (ms->treemap != 0 && (mem = tmalloc_small(ms, nb)) != 0) { + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + } + } + else if (bytes >= MAX_REQUEST) + nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ + else { + nb = pad_request(bytes); + if (ms->treemap != 0 && (mem = tmalloc_large(ms, nb)) != 0) { + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + } + + if (nb <= ms->dvsize) { + size_t rsize = ms->dvsize - nb; + mchunkptr p = ms->dv; + if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ + mchunkptr r = ms->dv = chunk_plus_offset(p, nb); + ms->dvsize = rsize; + set_size_and_pinuse_of_free_chunk(r, rsize); + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + } + else { /* exhaust dv */ + size_t dvs = ms->dvsize; + ms->dvsize = 0; + ms->dv = 0; + set_inuse_and_pinuse(ms, p, dvs); + } + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (nb < ms->topsize) { /* Split top */ + size_t rsize = ms->topsize -= nb; + mchunkptr p = ms->top; + mchunkptr r = ms->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + mem = chunk2mem(p); + check_top_chunk(ms, ms->top); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + mem = sys_alloc(ms, nb); + + postaction: + POSTACTION(ms); + return mem; + } + + return 0; +} + +void mspace_free(mspace msp, void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); +#if FOOTERS + mstate fm = get_mstate_for(p); + (void)msp; /* placate people compiling -Wunused */ +#else /* FOOTERS */ + mstate fm = (mstate)msp; +#endif /* FOOTERS */ + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } + if (!PREACTION(fm)) { + check_inuse_chunk(fm, p); + if (RTCHECK(ok_address(fm, p) && ok_inuse(p))) { + size_t psize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + size_t prevsize = p->prev_foot; + if (is_mmapped(p)) { + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + fm->footprint -= psize; + goto postaction; + } + else { + mchunkptr prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ + if (p != fm->dv) { + unlink_chunk(fm, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + fm->dvsize = psize; + set_free_with_pinuse(p, psize, next); + goto postaction; + } + } + else + goto erroraction; + } + } + + if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == fm->top) { + size_t tsize = fm->topsize += psize; + fm->top = p; + p->head = tsize | PINUSE_BIT; + if (p == fm->dv) { + fm->dv = 0; + fm->dvsize = 0; + } + if (should_trim(fm, tsize)) + sys_trim(fm, 0); + goto postaction; + } + else if (next == fm->dv) { + size_t dsize = fm->dvsize += psize; + fm->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + goto postaction; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(fm, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == fm->dv) { + fm->dvsize = psize; + goto postaction; + } + } + } + else + set_free_with_pinuse(p, psize, next); + + if (is_small(psize)) { + insert_small_chunk(fm, p, psize); + check_free_chunk(fm, p); + } + else { + tchunkptr tp = (tchunkptr)p; + insert_large_chunk(fm, tp, psize); + check_free_chunk(fm, p); + if (--fm->release_checks == 0) + release_unused_segments(fm); + } + goto postaction; + } + } + erroraction: + USAGE_ERROR_ACTION(fm, p); + postaction: + POSTACTION(fm); + } + } +} + +void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size) { + void* mem; + size_t req = 0; + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (n_elements != 0) { + req = n_elements * elem_size; + if (((n_elements | elem_size) & ~(size_t)0xffff) && + (req / n_elements != elem_size)) + req = MAX_SIZE_T; /* force downstream failure on overflow */ + } + mem = internal_malloc(ms, req); + if (mem != 0 && calloc_must_clear(mem2chunk(mem))) + memset(mem, 0, req); + return mem; +} + +void* mspace_realloc(mspace msp, void* oldmem, size_t bytes) { + void* mem = 0; + if (oldmem == 0) { + mem = mspace_malloc(msp, bytes); + } + else if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + } +#ifdef REALLOC_ZERO_BYTES_FREES + else if (bytes == 0) { + mspace_free(msp, oldmem); + } +#endif /* REALLOC_ZERO_BYTES_FREES */ + else { + size_t nb = request2size(bytes); + mchunkptr oldp = mem2chunk(oldmem); +#if ! FOOTERS + mstate m = (mstate)msp; +#else /* FOOTERS */ + mstate m = get_mstate_for(oldp); + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + if (!PREACTION(m)) { + mchunkptr newp = try_realloc_chunk(m, oldp, nb, 1); + POSTACTION(m); + if (newp != 0) { + check_inuse_chunk(m, newp); + mem = chunk2mem(newp); + } + else { + mem = mspace_malloc(m, bytes); + if (mem != 0) { + size_t oc = chunksize(oldp) - overhead_for(oldp); + memcpy(mem, oldmem, (oc < bytes)? oc : bytes); + mspace_free(m, oldmem); + } + } + } + } + return mem; +} + +void* mspace_realloc_in_place(mspace msp, void* oldmem, size_t bytes) { + void* mem = 0; + if (oldmem != 0) { + if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + } + else { + size_t nb = request2size(bytes); + mchunkptr oldp = mem2chunk(oldmem); +#if ! FOOTERS + mstate m = (mstate)msp; +#else /* FOOTERS */ + mstate m = get_mstate_for(oldp); + (void)msp; /* placate people compiling -Wunused */ + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + if (!PREACTION(m)) { + mchunkptr newp = try_realloc_chunk(m, oldp, nb, 0); + POSTACTION(m); + if (newp == oldp) { + check_inuse_chunk(m, newp); + mem = oldmem; + } + } + } + } + return mem; +} + +void* mspace_memalign(mspace msp, size_t alignment, size_t bytes) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (alignment <= MALLOC_ALIGNMENT) + return mspace_malloc(msp, bytes); + return internal_memalign(ms, alignment, bytes); +} + +void** mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]) { + size_t sz = elem_size; /* serves as 1-element array */ + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return ialloc(ms, n_elements, &sz, 3, chunks); +} + +void** mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return ialloc(ms, n_elements, sizes, 0, chunks); +} + +size_t mspace_bulk_free(mspace msp, void* array[], size_t nelem) { + return internal_bulk_free((mstate)msp, array, nelem); +} + +#if MALLOC_INSPECT_ALL +void mspace_inspect_all(mspace msp, + void(*handler)(void *start, + void *end, + size_t used_bytes, + void* callback_arg), + void* arg) { + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (!PREACTION(ms)) { + internal_inspect_all(ms, handler, arg); + POSTACTION(ms); + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } +} +#endif /* MALLOC_INSPECT_ALL */ + +int mspace_trim(mspace msp, size_t pad) { + int result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (!PREACTION(ms)) { + result = sys_trim(ms, pad); + POSTACTION(ms); + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +#if !NO_MALLOC_STATS +void mspace_malloc_stats(mspace msp) { + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + internal_malloc_stats(ms); + } + else { + USAGE_ERROR_ACTION(ms,ms); + } +} +#endif /* NO_MALLOC_STATS */ + +size_t mspace_footprint(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +size_t mspace_max_footprint(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->max_footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +size_t mspace_footprint_limit(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + size_t maf = ms->footprint_limit; + result = (maf == 0) ? MAX_SIZE_T : maf; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +size_t mspace_set_footprint_limit(mspace msp, size_t bytes) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (bytes == 0) + result = granularity_align(1); /* Use minimal size */ + if (bytes == MAX_SIZE_T) + result = 0; /* disable */ + else + result = granularity_align(bytes); + ms->footprint_limit = result; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +#if !NO_MALLINFO +struct mallinfo mspace_mallinfo(mspace msp) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + } + return internal_mallinfo(ms); +} +#endif /* NO_MALLINFO */ + +size_t mspace_usable_size(const void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (is_inuse(p)) + return chunksize(p) - overhead_for(p); + } + return 0; +} + +int mspace_mallopt(int param_number, int value) { + return change_mparam(param_number, value); +} + +#endif /* MSPACES */ + + +/* -------------------- Alternative MORECORE functions ------------------- */ + +/* + Guidelines for creating a custom version of MORECORE: + + * For best performance, MORECORE should allocate in multiples of pagesize. + * MORECORE may allocate more memory than requested. (Or even less, + but this will usually result in a malloc failure.) + * MORECORE must not allocate memory when given argument zero, but + instead return one past the end address of memory from previous + nonzero call. + * For best performance, consecutive calls to MORECORE with positive + arguments should return increasing addresses, indicating that + space has been contiguously extended. + * Even though consecutive calls to MORECORE need not return contiguous + addresses, it must be OK for malloc'ed chunks to span multiple + regions in those cases where they do happen to be contiguous. + * MORECORE need not handle negative arguments -- it may instead + just return MFAIL when given negative arguments. + Negative arguments are always multiples of pagesize. MORECORE + must not misinterpret negative args as large positive unsigned + args. You can suppress all such calls from even occurring by defining + MORECORE_CANNOT_TRIM, + + As an example alternative MORECORE, here is a custom allocator + kindly contributed for pre-OSX macOS. It uses virtually but not + necessarily physically contiguous non-paged memory (locked in, + present and won't get swapped out). You can use it by uncommenting + this section, adding some #includes, and setting up the appropriate + defines above: + + #define MORECORE osMoreCore + + There is also a shutdown routine that should somehow be called for + cleanup upon program exit. + + #define MAX_POOL_ENTRIES 100 + #define MINIMUM_MORECORE_SIZE (64 * 1024U) + static int next_os_pool; + void *our_os_pools[MAX_POOL_ENTRIES]; + + void *osMoreCore(int size) + { + void *ptr = 0; + static void *sbrk_top = 0; + + if (size > 0) + { + if (size < MINIMUM_MORECORE_SIZE) + size = MINIMUM_MORECORE_SIZE; + if (CurrentExecutionLevel() == kTaskLevel) + ptr = PoolAllocateResident(size + RM_PAGE_SIZE, 0); + if (ptr == 0) + { + return (void *) MFAIL; + } + // save ptrs so they can be freed during cleanup + our_os_pools[next_os_pool] = ptr; + next_os_pool++; + ptr = (void *) ((((size_t) ptr) + RM_PAGE_MASK) & ~RM_PAGE_MASK); + sbrk_top = (char *) ptr + size; + return ptr; + } + else if (size < 0) + { + // we don't currently support shrink behavior + return (void *) MFAIL; + } + else + { + return sbrk_top; + } + } + + // cleanup any allocated memory pools + // called as last thing before shutting down driver + + void osCleanupMem(void) + { + void **ptr; + + for (ptr = our_os_pools; ptr < &our_os_pools[MAX_POOL_ENTRIES]; ptr++) + if (*ptr) + { + PoolDeallocate(*ptr); + *ptr = 0; + } + } + +*/ + + +/* ----------------------------------------------------------------------- +History: + v2.8.6 Wed Aug 29 06:57:58 2012 Doug Lea + * fix bad comparison in dlposix_memalign + * don't reuse adjusted asize in sys_alloc + * add LOCK_AT_FORK -- thanks to Kirill Artamonov for the suggestion + * reduce compiler warnings -- thanks to all who reported/suggested these + + v2.8.5 Sun May 22 10:26:02 2011 Doug Lea (dl at gee) + * Always perform unlink checks unless INSECURE + * Add posix_memalign. + * Improve realloc to expand in more cases; expose realloc_in_place. + Thanks to Peter Buhr for the suggestion. + * Add footprint_limit, inspect_all, bulk_free. Thanks + to Barry Hayes and others for the suggestions. + * Internal refactorings to avoid calls while holding locks + * Use non-reentrant locks by default. Thanks to Roland McGrath + for the suggestion. + * Small fixes to mspace_destroy, reset_on_error. + * Various configuration extensions/changes. Thanks + to all who contributed these. + + V2.8.4a Thu Apr 28 14:39:43 2011 (dl at gee.cs.oswego.edu) + * Update Creative Commons URL + + V2.8.4 Wed May 27 09:56:23 2009 Doug Lea (dl at gee) + * Use zeros instead of prev foot for is_mmapped + * Add mspace_track_large_chunks; thanks to Jean Brouwers + * Fix set_inuse in internal_realloc; thanks to Jean Brouwers + * Fix insufficient sys_alloc padding when using 16byte alignment + * Fix bad error check in mspace_footprint + * Adaptations for ptmalloc; thanks to Wolfram Gloger. + * Reentrant spin locks; thanks to Earl Chew and others + * Win32 improvements; thanks to Niall Douglas and Earl Chew + * Add NO_SEGMENT_TRAVERSAL and MAX_RELEASE_CHECK_RATE options + * Extension hook in malloc_state + * Various small adjustments to reduce warnings on some compilers + * Various configuration extensions/changes for more platforms. Thanks + to all who contributed these. + + V2.8.3 Thu Sep 22 11:16:32 2005 Doug Lea (dl at gee) + * Add max_footprint functions + * Ensure all appropriate literals are size_t + * Fix conditional compilation problem for some #define settings + * Avoid concatenating segments with the one provided + in create_mspace_with_base + * Rename some variables to avoid compiler shadowing warnings + * Use explicit lock initialization. + * Better handling of sbrk interference. + * Simplify and fix segment insertion, trimming and mspace_destroy + * Reinstate REALLOC_ZERO_BYTES_FREES option from 2.7.x + * Thanks especially to Dennis Flanagan for help on these. + + V2.8.2 Sun Jun 12 16:01:10 2005 Doug Lea (dl at gee) + * Fix memalign brace error. + + V2.8.1 Wed Jun 8 16:11:46 2005 Doug Lea (dl at gee) + * Fix improper #endif nesting in C++ + * Add explicit casts needed for C++ + + V2.8.0 Mon May 30 14:09:02 2005 Doug Lea (dl at gee) + * Use trees for large bins + * Support mspaces + * Use segments to unify sbrk-based and mmap-based system allocation, + removing need for emulation on most platforms without sbrk. + * Default safety checks + * Optional footer checks. Thanks to William Robertson for the idea. + * Internal code refactoring + * Incorporate suggestions and platform-specific changes. + Thanks to Dennis Flanagan, Colin Plumb, Niall Douglas, + Aaron Bachmann, Emery Berger, and others. + * Speed up non-fastbin processing enough to remove fastbins. + * Remove useless cfree() to avoid conflicts with other apps. + * Remove internal memcpy, memset. Compilers handle builtins better. + * Remove some options that no one ever used and rename others. + + V2.7.2 Sat Aug 17 09:07:30 2002 Doug Lea (dl at gee) + * Fix malloc_state bitmap array misdeclaration + + V2.7.1 Thu Jul 25 10:58:03 2002 Doug Lea (dl at gee) + * Allow tuning of FIRST_SORTED_BIN_SIZE + * Use PTR_UINT as type for all ptr->int casts. Thanks to John Belmonte. + * Better detection and support for non-contiguousness of MORECORE. + Thanks to Andreas Mueller, Conal Walsh, and Wolfram Gloger + * Bypass most of malloc if no frees. Thanks To Emery Berger. + * Fix freeing of old top non-contiguous chunk im sysmalloc. + * Raised default trim and map thresholds to 256K. + * Fix mmap-related #defines. Thanks to Lubos Lunak. + * Fix copy macros; added LACKS_FCNTL_H. Thanks to Neal Walfield. + * Branch-free bin calculation + * Default trim and mmap thresholds now 256K. + + V2.7.0 Sun Mar 11 14:14:06 2001 Doug Lea (dl at gee) + * Introduce independent_comalloc and independent_calloc. + Thanks to Michael Pachos for motivation and help. + * Make optional .h file available + * Allow > 2GB requests on 32bit systems. + * new WIN32 sbrk, mmap, munmap, lock code from . + Thanks also to Andreas Mueller , + and Anonymous. + * Allow override of MALLOC_ALIGNMENT (Thanks to Ruud Waij for + helping test this.) + * memalign: check alignment arg + * realloc: don't try to shift chunks backwards, since this + leads to more fragmentation in some programs and doesn't + seem to help in any others. + * Collect all cases in malloc requiring system memory into sysmalloc + * Use mmap as backup to sbrk + * Place all internal state in malloc_state + * Introduce fastbins (although similar to 2.5.1) + * Many minor tunings and cosmetic improvements + * Introduce USE_PUBLIC_MALLOC_WRAPPERS, USE_MALLOC_LOCK + * Introduce MALLOC_FAILURE_ACTION, MORECORE_CONTIGUOUS + Thanks to Tony E. Bennett and others. + * Include errno.h to support default failure action. + + V2.6.6 Sun Dec 5 07:42:19 1999 Doug Lea (dl at gee) + * return null for negative arguments + * Added Several WIN32 cleanups from Martin C. Fong + * Add 'LACKS_SYS_PARAM_H' for those systems without 'sys/param.h' + (e.g. WIN32 platforms) + * Cleanup header file inclusion for WIN32 platforms + * Cleanup code to avoid Microsoft Visual C++ compiler complaints + * Add 'USE_DL_PREFIX' to quickly allow co-existence with existing + memory allocation routines + * Set 'malloc_getpagesize' for WIN32 platforms (needs more work) + * Use 'assert' rather than 'ASSERT' in WIN32 code to conform to + usage of 'assert' in non-WIN32 code + * Improve WIN32 'sbrk()' emulation's 'findRegion()' routine to + avoid infinite loop + * Always call 'fREe()' rather than 'free()' + + V2.6.5 Wed Jun 17 15:57:31 1998 Doug Lea (dl at gee) + * Fixed ordering problem with boundary-stamping + + V2.6.3 Sun May 19 08:17:58 1996 Doug Lea (dl at gee) + * Added pvalloc, as recommended by H.J. Liu + * Added 64bit pointer support mainly from Wolfram Gloger + * Added anonymously donated WIN32 sbrk emulation + * Malloc, calloc, getpagesize: add optimizations from Raymond Nijssen + * malloc_extend_top: fix mask error that caused wastage after + foreign sbrks + * Add linux mremap support code from HJ Liu + + V2.6.2 Tue Dec 5 06:52:55 1995 Doug Lea (dl at gee) + * Integrated most documentation with the code. + * Add support for mmap, with help from + Wolfram Gloger (Gloger@lrz.uni-muenchen.de). + * Use last_remainder in more cases. + * Pack bins using idea from colin@nyx10.cs.du.edu + * Use ordered bins instead of best-fit threshhold + * Eliminate block-local decls to simplify tracing and debugging. + * Support another case of realloc via move into top + * Fix error occuring when initial sbrk_base not word-aligned. + * Rely on page size for units instead of SBRK_UNIT to + avoid surprises about sbrk alignment conventions. + * Add mallinfo, mallopt. Thanks to Raymond Nijssen + (raymond@es.ele.tue.nl) for the suggestion. + * Add `pad' argument to malloc_trim and top_pad mallopt parameter. + * More precautions for cases where other routines call sbrk, + courtesy of Wolfram Gloger (Gloger@lrz.uni-muenchen.de). + * Added macros etc., allowing use in linux libc from + H.J. Lu (hjl@gnu.ai.mit.edu) + * Inverted this history list + + V2.6.1 Sat Dec 2 14:10:57 1995 Doug Lea (dl at gee) + * Re-tuned and fixed to behave more nicely with V2.6.0 changes. + * Removed all preallocation code since under current scheme + the work required to undo bad preallocations exceeds + the work saved in good cases for most test programs. + * No longer use return list or unconsolidated bins since + no scheme using them consistently outperforms those that don't + given above changes. + * Use best fit for very large chunks to prevent some worst-cases. + * Added some support for debugging + + V2.6.0 Sat Nov 4 07:05:23 1995 Doug Lea (dl at gee) + * Removed footers when chunks are in use. Thanks to + Paul Wilson (wilson@cs.texas.edu) for the suggestion. + + V2.5.4 Wed Nov 1 07:54:51 1995 Doug Lea (dl at gee) + * Added malloc_trim, with help from Wolfram Gloger + (wmglo@Dent.MED.Uni-Muenchen.DE). + + V2.5.3 Tue Apr 26 10:16:01 1994 Doug Lea (dl at g) + + V2.5.2 Tue Apr 5 16:20:40 1994 Doug Lea (dl at g) + * realloc: try to expand in both directions + * malloc: swap order of clean-bin strategy; + * realloc: only conditionally expand backwards + * Try not to scavenge used bins + * Use bin counts as a guide to preallocation + * Occasionally bin return list chunks in first scan + * Add a few optimizations from colin@nyx10.cs.du.edu + + V2.5.1 Sat Aug 14 15:40:43 1993 Doug Lea (dl at g) + * faster bin computation & slightly different binning + * merged all consolidations to one part of malloc proper + (eliminating old malloc_find_space & malloc_clean_bin) + * Scan 2 returns chunks (not just 1) + * Propagate failure in realloc if malloc returns 0 + * Add stuff to allow compilation on non-ANSI compilers + from kpv@research.att.com + + V2.5 Sat Aug 7 07:41:59 1993 Doug Lea (dl at g.oswego.edu) + * removed potential for odd address access in prev_chunk + * removed dependency on getpagesize.h + * misc cosmetics and a bit more internal documentation + * anticosmetics: mangled names in macros to evade debugger strangeness + * tested on sparc, hp-700, dec-mips, rs6000 + with gcc & native cc (hp, dec only) allowing + Detlefs & Zorn comparison study (in SIGPLAN Notices.) + + Trial version Fri Aug 28 13:14:29 1992 Doug Lea (dl at g.oswego.edu) + * Based loosely on libg++-1.2X malloc. (It retains some of the overall + structure of old version, but most details differ.) + +*/ diff --git a/service/rustunnel/c_src/malloc.c b/service/rustunnel/c_src/malloc.c new file mode 100644 index 0000000..76027f6 --- /dev/null +++ b/service/rustunnel/c_src/malloc.c @@ -0,0 +1,12 @@ +#define USE_LOCKS 1 +#define USE_SPIN_LOCKS 1 +#define LOCK_AT_FORK 1 +#define HAVE_MMAP 0 +#define HAVE_MREMAP 0 +#define USE_DEV_RANDOM 1 +#define NO_MALLOC_STATS 1 + +#include +#include +#include +#include diff --git a/service/rustunnel/src/lib.rs b/service/rustunnel/src/lib.rs new file mode 100644 index 0000000..e94f4d3 --- /dev/null +++ b/service/rustunnel/src/lib.rs @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +pub mod logger; +mod proxy; +pub mod seccomp; +pub mod stream; +pub mod tls; +pub mod util; + +use std::os::unix::prelude::*; + +use failure::{ResultExt}; +use log::{error, warn, debug}; +use nix::errno::{Errno}; +use nix::poll::*; + +use self::proxy::*; +use self::seccomp::*; +use self::stream::*; +use self::tls::*; + +pub struct ServerChild { + tls_acceptor: TlsAcceptor, + source_tcp_stream: ProxyTcpStream, + target_pipe_stream: ProxyPipeStream, +} + +pub struct ClientChild { + tls_connector: TlsConnector, + source_pipe_stream: ProxyPipeStream, + target_tcp_stream: ProxyTcpStream, +} + +pub type Identity = tls::Identity; + +// +// ServerChild impls +// + +impl ServerChild { + pub fn new(tls_ca_cert: CaCertificate, + tls_identity: Identity, + source_tcp_stream: ProxyTcpStream, + target_pipe_stream: ProxyPipeStream) + -> Result + { + let tls_acceptor = TlsAcceptor::new(tls_identity, tls_ca_cert).context("error setting up tls acceptor")?; + + Ok(ServerChild { tls_acceptor, source_tcp_stream, target_pipe_stream }) + } + + pub fn run(mut self) -> Result<(), failure::Error> { + setup_seccomp().context("error setting up seccomp")?; + + match handshake(self.tls_acceptor.accept(self.source_tcp_stream)) { + Ok(mut source_tls_stream) => { + let _ignore = proxy("local target", &mut self.target_pipe_stream, "remote source", &mut source_tls_stream); + Ok(()) + } + Err(()) => Ok(()), + } + } +} + +// +// ClientChild impls +// + +impl ClientChild { + pub fn new(tls_hostname: TlsHostname, + tls_ca_certs: Vec, + tls_identity: Option, + source_pipe_stream: ProxyPipeStream, + target_tcp_stream: ProxyTcpStream) + -> Result + { + let tls_connector = TlsConnector::new(tls_identity, tls_hostname, tls_ca_certs).context("error setting up tls connector")?; + Ok(Self { tls_connector, source_pipe_stream, target_tcp_stream }) + } + + pub fn run(mut self) -> Result<(), failure::Error> { + setup_seccomp().context("error setting up seccomp")?; + + debug!("starting TLS handshake"); + match handshake(self.tls_connector.connect(self.target_tcp_stream)) { + Ok(mut target_tls_stream) => { + debug!("finished TLS handshake"); + let _ignore = proxy("local source", &mut self.source_pipe_stream, "remote target", &mut target_tls_stream); + Ok(()) + } + Err(()) => Ok(()), + } + } +} + +// +// internal +// + +fn handshake(mut accept_result: Result, HandshakeError>) -> Result, ()> { + loop { + let (stream, poll_flags) = match accept_result { + Ok(tls_stream) => return Ok(tls_stream), + Err(HandshakeError::WantRead(stream)) => (stream, EventFlags::POLLIN), + Err(HandshakeError::WantWrite(stream)) => (stream, EventFlags::POLLOUT), + Err(HandshakeError::Failure(error)) => { + warn!("handshake error: {}", error); + return Err(()); + } + }; + let mut poll_fds = [ + PollFd::new(stream.as_raw_fd(), poll_flags), + ]; + + // XXX handshake timeout + match poll(&mut poll_fds, -1) { + Ok(_event_count) => (), + Err(nix::Error::Sys(Errno::EINTR)) => (), + Err(error) => { + error!("error polling sockets: {}", error); + return Err(()); + } + } + accept_result = stream.handshake(); + } +} + +fn proxy(stream_0_name: &'static str, + stream_0: &mut ProxyPipeStream, + stream_1_name: &'static str, + stream_1: &mut (impl ProxyRead + ProxyWrite + AsRawFd)) + -> Result<(), ()> +{ + let mut buffer_0 = ProxyBuffer::new(); + let mut buffer_1 = ProxyBuffer::new(); + + loop { + let mut stream_0_flags = EventFlags::empty(); + let mut stream_1_flags = EventFlags::empty(); + + let buffer_0_flags = buffer_0.proxy(stream_0_name, stream_0, stream_1_name, stream_1)?; + stream_0_flags |= buffer_0_flags.0; + stream_1_flags |= buffer_0_flags.1; + + let buffer_1_flags = buffer_1.proxy(stream_1_name, stream_1, stream_0_name, stream_0)?; + stream_0_flags |= buffer_1_flags.1; + stream_1_flags |= buffer_1_flags.0; + + if buffer_0.is_closed() && buffer_1.is_closed() { + break; + } + + let stream_0_write_fd = stream_0.write_fd().unwrap_or(-1); + + fn new_poll_fd(mut fd: RawFd, flags: EventFlags) -> PollFd { + if flags.is_empty() { + fd = -1; + } + PollFd::new(fd, flags) + } + let mut poll_fds = [ + new_poll_fd(stream_0.read_fd(), stream_0_flags & EventFlags::POLLIN), + new_poll_fd(stream_0_write_fd, stream_0_flags & EventFlags::POLLOUT), + new_poll_fd(stream_1.as_raw_fd(), stream_1_flags), + ]; + + debug!("polling: {:?}", &poll_fds); + match poll(&mut poll_fds, -1) { + Ok(_event_count) => (), + Err(nix::Error::Sys(Errno::EINTR)) => continue, + Err(error) => { + error!("error polling sockets: {}", error); + return Err(()); + } + } + } + Ok(()) +} + +macro_rules! cstr { + ($str:literal) => { + std::ffi::CStr::from_bytes_with_nul(concat!($str, "\0").as_bytes()).expect("cstr macro bug") + }; +} + +fn setup_seccomp() -> Result<(), failure::Error> { + configure_openssl_for_seccomp()?; + + let mut seccomp = SeccompContext::new() + .map_err(|()| failure::format_err!("error creating seccomp context"))?; + + let () = seccomp.allow(cstr!("poll"))?; + let () = seccomp.allow(cstr!("read"))?; + let () = seccomp.allow(cstr!("write"))?; + let () = seccomp.allow(cstr!("shutdown"))?; + let () = seccomp.allow(cstr!("close"))?; + let () = seccomp.allow(cstr!("exit"))?; + let () = seccomp.allow(cstr!("exit_group"))?; + let () = seccomp.allow(cstr!("sigreturn"))?; + let () = seccomp.allow(cstr!("munmap"))?; + let () = seccomp.allow(cstr!("brk"))?; + let () = seccomp.allow(cstr!("futex"))?; + let () = seccomp.allow(cstr!("restart_syscall"))?; + let () = seccomp.allow(cstr!("sched_yield"))?; + let () = seccomp.allow(cstr!("pause"))?; + // XXX allow sigaction/sigprocmask/sigtimedwait/sigaltstack? + // XXX allow restricted prctl? (used in glibc) + + let () = seccomp.deny_errno(cstr!("openat"), Errno::ENOSYS)?; + let () = seccomp.deny_errno(cstr!("sigaltstack"), Errno::ENOSYS)?; + + seccomp::configure_panic_hook(); + let () = seccomp.load()?; + Ok(()) +} diff --git a/service/rustunnel/src/logger.rs b/service/rustunnel/src/logger.rs new file mode 100644 index 0000000..71b04a4 --- /dev/null +++ b/service/rustunnel/src/logger.rs @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::io; +use std::io::prelude::*; + +pub struct Logger { + pub level: log::Level, +} + +impl log::Log for Logger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= self.level + } + + fn log(&self, record: &log::Record) { + if self.enabled(record.metadata()) { + let log_level_string = match record.level() { + log::Level::Error => "ERRO", + log::Level::Warn => "WARN", + log::Level::Info => "INFO", + log::Level::Debug => "DEBG", + log::Level::Trace => "TRCE", + }; + let line = format!("{:<4} {}\n", log_level_string, record.args()); + let _ignore = write!(io::stderr(), "{}", line); + } + } + + fn flush(&self) { + } +} + +pub fn parse_line(line: &str) -> (log::Level, &str) { + match line.get(..5) { + Some("ERRO ") => (log::Level::Error, line.get(5..).unwrap_or_else(|| unreachable!())), + Some("WARN ") => (log::Level::Warn, line.get(5..).unwrap_or_else(|| unreachable!())), + Some("INFO ") => (log::Level::Info, line.get(5..).unwrap_or_else(|| unreachable!())), + Some("DEBG ") => (log::Level::Debug, line.get(5..).unwrap_or_else(|| unreachable!())), + Some("TRCE ") => (log::Level::Trace, line.get(5..).unwrap_or_else(|| unreachable!())), + _ => (log::Level::Info, &line[..]), + } +} diff --git a/service/rustunnel/src/proxy.rs b/service/rustunnel/src/proxy.rs new file mode 100644 index 0000000..c1ab2c7 --- /dev/null +++ b/service/rustunnel/src/proxy.rs @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::io; + +use log::{info, debug}; +use nix::poll::*; + +use super::stream::*; + +pub struct ProxyBuffer { + buffer: Box<[u8; BUFFER_SIZE]>, + length: usize, + position: usize, + read_closed: bool, + write_closed: bool, +} + +const BUFFER_SIZE: usize = 32 * 1024; + +// +// ProxyBuffer impls +// + +impl ProxyBuffer { + pub fn new() -> Self { + Self { + buffer: Box::new([0; BUFFER_SIZE]), + length: 0, + position: 0, + read_closed: false, + write_closed: false, + } + } + + pub fn is_closed(&self) -> bool { + self.read_closed && self.write_closed + } + + pub fn proxy(&mut self, + in_name: &'static str, + in_stream: &mut impl ProxyRead, + out_name: &'static str, + out_stream: &mut impl ProxyWrite) + -> Result<(EventFlags, EventFlags), ()> + { + while !self.is_closed() { + match self.proxy_read(in_name, in_stream) { + Ok(()) => (), + Err(ProxyStreamError::WantRead) => return Ok((EventFlags::POLLIN, EventFlags::empty())), + Err(ProxyStreamError::WantWrite) => return Ok((EventFlags::POLLOUT, EventFlags::empty())), + Err(ProxyStreamError::Io(_)) => return Err(()), + } + match self.proxy_write(out_name, out_stream) { + Ok(()) => (), + Err(ProxyStreamError::WantRead) => return Ok((EventFlags::empty(), EventFlags::POLLIN)), + Err(ProxyStreamError::WantWrite) => return Ok((EventFlags::empty(), EventFlags::POLLOUT)), + Err(ProxyStreamError::Io(_)) => return Err(()), + } + } + Ok((EventFlags::empty(), EventFlags::empty())) + } + fn proxy_read(&mut self, + in_name: &'static str, + in_stream: &mut impl ProxyRead) + -> Result<(), ProxyStreamError> + { + loop { + let read_result = match self.read_from(in_stream) { + Ok(0) => { + info!("connection closed by {}", in_name); + self.read_closed = true; + Ok(()) + } + Ok(bytes_read) => { + debug!("{} bytes in buffer from {}", bytes_read, in_name); + Ok(()) + } + Err(error @ ProxyStreamError::WantRead) | + Err(error @ ProxyStreamError::WantWrite) => + Err(error), + Err(ProxyStreamError::Io(ref error)) if error.kind() == io::ErrorKind::Interrupted => + continue, + Err(ProxyStreamError::Io(error)) => { + if error.kind() == io::ErrorKind::UnexpectedEof { + info!("connection closed ungracefully by {}: {}", in_name, error); + } else { + info!("error reading from {}: {}", in_name, error); + } + Err(ProxyStreamError::Io(error)) + } + }; + break read_result; + } + } + fn proxy_write(&mut self, + out_name: &'static str, + out_stream: &mut impl ProxyWrite) + -> Result<(), ProxyStreamError> + { + loop { + let write_result = match self.write_to(out_stream) { + Ok(wrote_bytes) => { + if wrote_bytes != 0 { + debug!("wrote {} bytes to {}", wrote_bytes, out_name); + } + + if self.position == self.length { + if self.read_closed && !self.write_closed { + match out_stream.shutdown() { + Ok(()) => { + debug!("shut down connection to {}", out_name); + self.write_closed = true; + Ok(()) + } + Err(error @ ProxyStreamError::WantRead) | + Err(error @ ProxyStreamError::WantWrite) => + Err(error), + Err(ProxyStreamError::Io(ref error)) if error.kind() == io::ErrorKind::Interrupted => + continue, + Err(ProxyStreamError::Io(error)) => { + info!("error shutting down connection to {}: {}", out_name, error); + Err(ProxyStreamError::Io(error)) + } + } + } else { + Ok(()) + } + } else { + continue + } + } + Err(error @ ProxyStreamError::WantRead) | + Err(error @ ProxyStreamError::WantWrite) => + Err(error), + Err(ProxyStreamError::Io(ref error)) if error.kind() == io::ErrorKind::Interrupted => + continue, + Err(ProxyStreamError::Io(error)) => { + info!("error writing to {}: {}", out_name, error); + Err(ProxyStreamError::Io(error)) + } + }; + break write_result; + } + } + + pub fn read_from(&mut self, stream: &mut impl ProxyRead) -> Result { + if self.position == self.length { + self.length = stream.read(&mut self.buffer[..])?; + self.position = 0; + } else { + stream.read(&mut [])?; + } + Ok(self.length - self.position) + } + + pub fn write_to(&mut self, stream: &mut impl ProxyWrite) -> Result { + if self.position < self.length { + let wrote_bytes = stream.write(&mut self.buffer[self.position..self.length])?; + self.position = self.position.saturating_add(wrote_bytes); + assert!(self.position <= self.length); + Ok(wrote_bytes) + } else { + Ok(0) + } + } +} diff --git a/service/rustunnel/src/seccomp.rs b/service/rustunnel/src/seccomp.rs new file mode 100644 index 0000000..1a6d5a1 --- /dev/null +++ b/service/rustunnel/src/seccomp.rs @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::collections::*; +use std::ffi::{CStr}; +use std::fs; +use std::os::unix::prelude::*; +use std::path::{Path}; +use std::ptr::{NonNull}; +use std::panic; + +use failure::{format_err, ResultExt}; +use nix::errno::{Errno}; +use nix::unistd; +use seccomp_sys::*; + +use crate::util; + +pub struct SeccompContext { + context: NonNull, +} + +pub fn configure_malloc() -> Result<(), failure::Error> { + // mainly to ensure dlmalloc is initialized + if unsafe { libc::mallopt(libc::M_MMAP_THRESHOLD, 0) } != 1 { + return Err(failure::format_err!("error setting mallopt M_MMAP_THRESHOLD to 0")); + } + + Ok(()) +} + +pub fn close_all_fds(keep_fds: &BTreeSet) -> Result<(), failure::Error> { + let fd_dir = fs::read_dir(Path::new(r"/proc/self/fd/")).context("error reading /proc/self/fd/")?; + let mut fds = BTreeSet::new(); + for dir_entry_result in fd_dir { + let fd_name = dir_entry_result.context("error reading /proc/self/fd/")?.file_name(); + let fd = fd_name.to_string_lossy().parse::().with_context(|_| format_err!("invalid fd number in /proc/self/fd/: {:?}", fd_name))?; + fds.insert(fd); + } + for fd in fds.difference(&keep_fds) { + match unistd::close(*fd) { + Ok(()) => (), + Err(nix::Error::Sys(Errno::EBADF)) => (), + Err(error) => util::convert_nix(Err(error))?, + } + } + Ok(()) +} + +pub fn configure_panic_hook() { + let default_panic_hook = panic::take_hook(); + + panic::set_hook(Box::new(move |panic_info: &panic::PanicInfo<'_>| { + default_panic_hook(panic_info); + // trigger abort via double panic + panic!("aborting") + })); +} + +// +// SeccompContext impls +// + +impl SeccompContext { + pub fn new() -> Result { + let context = NonNull::new(unsafe { seccomp_init(SCMP_ACT_KILL_PROCESS) }).ok_or(())?; + Ok(Self { context }) + } + pub fn allow(&mut self, syscall_name: &CStr) -> Result<(), Errno> { + let syscall_nr = unsafe { seccomp_syscall_resolve_name(syscall_name.as_ptr() ) }; + assert_eq!(0, errno_result(unsafe { seccomp_rule_add(self.context.as_ptr(), SCMP_ACT_ALLOW, syscall_nr, 0) })?); + Ok(()) + } + pub fn deny_errno(&mut self, syscall_name: &CStr, errno: Errno) -> Result<(), Errno> { + let syscall_nr = unsafe { seccomp_syscall_resolve_name(syscall_name.as_ptr() ) }; + assert_eq!(0, errno_result(unsafe { seccomp_rule_add(self.context.as_ptr(), SCMP_ACT_ERRNO(errno as u32), syscall_nr, 0) })?); + Ok(()) + } + pub fn load(&mut self) -> Result<(), Errno> { + assert_eq!(0, errno_result(unsafe { seccomp_load(self.context.as_ptr()) })?); + Ok(()) + } +} + +impl Drop for SeccompContext { + fn drop(&mut self) { + unsafe { seccomp_release(self.context.as_ptr()) }; + } +} + +// +// internal +// + +fn errno_result(result: i32) -> Result { + if result >= 0 { + Ok(result) + } else { + Err(Errno::from_i32(-result)) + } +} diff --git a/service/rustunnel/src/stream.rs b/service/rustunnel/src/stream.rs new file mode 100644 index 0000000..7350a4c --- /dev/null +++ b/service/rustunnel/src/stream.rs @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::io; +use std::io::prelude::*; +use std::net::{TcpStream}; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; + +use nix::unistd; + +use crate::util; + +pub struct ProxyTcpStream { + fd: RawFd, +} + +pub struct ProxyPipeStream { + read_fd: RawFd, + write_fd: Option, +} + +pub trait ProxyRead { + fn read(&mut self, buf: &mut [u8]) -> Result; +} +pub trait ProxyWrite { + fn write(&mut self, buf: &[u8]) -> Result; + fn shutdown(&mut self) -> Result<(), ProxyStreamError>; +} + +#[derive(Debug)] +pub enum ProxyStreamError { + WantRead, + WantWrite, + Io(io::Error), +} + +// +// ProxyTcpStream impls +// + +impl ProxyTcpStream { + pub fn from_std(stream: TcpStream) -> io::Result { + stream.set_nodelay(true)?; + stream.set_nonblocking(true)?; + Ok(Self { + fd: stream.into_raw_fd(), + }) + } +} + +impl Read for ProxyTcpStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + <&Self>::read(&mut &*self, buf) + } +} +impl Write for ProxyTcpStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + <&Self>::write(&mut &*self, buf) + } + fn flush(&mut self) -> io::Result<()> { + <&Self>::flush(&mut &*self) + } +} + +impl Read for &'_ ProxyTcpStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + util::convert_nix(unistd::read(self.fd, buf)) + } +} + +impl Write for &'_ ProxyTcpStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + util::convert_nix(unistd::write(self.fd, buf)) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl AsRawFd for ProxyTcpStream { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +impl Drop for ProxyTcpStream { + fn drop(&mut self) { + let _ignore = unistd::close(self.fd); + } +} + +// +// ProxyPipeStream +// + +impl ProxyPipeStream { + pub fn new(read_fd: RawFd, write_fd: RawFd) -> io::Result { + util::set_nonblocking(read_fd)?; + util::set_nonblocking(write_fd)?; + Ok(Self { + read_fd, + write_fd: Some(write_fd), + }) + } + pub fn stdio() -> io::Result { + let stdin = Box::leak(Box::new(io::stdin())); + let _stdin_lock = Box::leak(Box::new(stdin.lock())); + let stdout = Box::leak(Box::new(io::stdout())); + let _stdout_lock = Box::leak(Box::new(stdout.lock())); + + Self::new(libc::STDIN_FILENO, libc::STDOUT_FILENO) + } + + pub fn read_fd(&self) -> RawFd { + self.read_fd + } + + pub fn write_fd(&self) -> Option { + self.write_fd + } +} + +impl Read for ProxyPipeStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + <&Self>::read(&mut &*self, buf) + } +} +impl Write for ProxyPipeStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + <&Self>::write(&mut &*self, buf) + } + fn flush(&mut self) -> io::Result<()> { + <&Self>::flush(&mut &*self) + } +} + +impl Read for &'_ ProxyPipeStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + util::convert_nix(unistd::read(self.read_fd, buf)) + } +} + +impl Write for &'_ ProxyPipeStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self.write_fd { + Some(write_fd) => util::convert_nix(unistd::write(write_fd, buf)), + None => Err(io::ErrorKind::NotConnected.into()), + } + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl ProxyRead for ProxyPipeStream { + fn read(&mut self, buf: &mut [u8]) -> Result { + <&Self as Read>::read(&mut &*self, buf).map_err(|error: io::Error| { + if error.kind() == io::ErrorKind::WouldBlock { + ProxyStreamError::WantRead + } else { + ProxyStreamError::Io(error) + } + }) + } +} + +impl ProxyWrite for ProxyPipeStream { + fn write(&mut self, buf: &[u8]) -> Result { + <&Self as Write>::write(&mut &*self, buf).map_err(|error: io::Error| { + if error.kind() == io::ErrorKind::WouldBlock { + ProxyStreamError::WantWrite + } else { + ProxyStreamError::Io(error) + } + }) + } + fn shutdown(&mut self) -> Result<(), ProxyStreamError> { + if let Some(write_fd) = self.write_fd.take() { + util::convert_nix(unistd::close(write_fd)).map_err(ProxyStreamError::Io) + } else { + Ok(()) + } + } +} + +impl Drop for ProxyPipeStream { + fn drop(&mut self) { + if let Some(write_fd) = self.write_fd { + let _ignore = unistd::close(write_fd); + } + let _ignore = unistd::close(self.read_fd); + } +} diff --git a/service/rustunnel/src/tls.rs b/service/rustunnel/src/tls.rs new file mode 100644 index 0000000..4139991 --- /dev/null +++ b/service/rustunnel/src/tls.rs @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::collections::{HashSet}; +use std::fs; +use std::fs::{File}; +use std::io; +use std::io::prelude::*; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::path::Path; + +use failure::{ResultExt}; +use log::{warn}; +use openssl::ssl; +use openssl::pkcs12; +use openssl::x509; + +use super::stream::{ProxyRead, ProxyWrite, ProxyStreamError}; + +pub struct TlsAcceptor { + acceptor: ssl::SslAcceptor, +} + +pub struct TlsConnector { + connector: ssl::SslConnector, + hostname: TlsHostname, +} + +pub enum TlsHostname { + AcceptInvalid, + Hostname(String), +} + +pub struct Identity { + pkcs12: pkcs12::ParsedPkcs12, +} + +pub enum CaCertificate { + System, + Custom { + x509: x509::X509, + }, +} + +pub struct MidHandshakeTlsStream { + stream: ssl::MidHandshakeSslStream, +} + +pub struct TlsStream { + stream: ssl::SslStream, +} + +pub enum HandshakeError { + Failure(HandshakeFailure), + WantRead(MidHandshakeTlsStream), + WantWrite(MidHandshakeTlsStream), +} + +#[derive(Debug, failure::Fail)] +pub enum HandshakeFailure { + #[fail(display = "{} ({})", _0, _1)] + VerifyError(ssl::Error, x509::X509VerifyResult), + #[fail(display = "{}", _0)] + SetupError(openssl::error::ErrorStack), + #[fail(display = "{}", _0)] + OtherError(ssl::Error), +} + +pub fn configure_openssl_for_seccomp() -> Result<(), failure::Error> { + openssl::rand::keep_random_devices_open(true); + openssl::rand::rand_bytes(&mut [0; 1]).context("error setting up openssl rand")?; + Ok(()) +} + +// +// TlsAcceptor impls +// + +impl TlsAcceptor { + pub fn new(tls_identity: Identity, tls_ca_cert: CaCertificate) -> Result { + let mut acceptor = ssl::SslAcceptor::mozilla_intermediate_v5(ssl::SslMethod::tls()).context("error creating acceptor")?; + + acceptor.set_private_key(&tls_identity.pkcs12.pkey).context("error setting server private key")?; + acceptor.set_certificate(&tls_identity.pkcs12.cert).context("error setting server certificate")?; + if let Some(chain) = tls_identity.pkcs12.chain { + for cert in chain.iter().rev() { + acceptor.add_extra_chain_cert(cert.to_owned()).context("error adding server certificate chain")?; + } + } + + acceptor.set_min_proto_version(Some(ssl::SslVersion::TLS1_2)).context("error setting minimum tls version")?; + acceptor.set_max_proto_version(Some(ssl::SslVersion::TLS1_2)).context("error setting maximum tls version")?; + + acceptor.set_session_cache_mode(ssl::SslSessionCacheMode::OFF); + + let mut verify_mode = ssl::SslVerifyMode::empty(); + verify_mode.insert(ssl::SslVerifyMode::PEER); + verify_mode.insert(ssl::SslVerifyMode::FAIL_IF_NO_PEER_CERT); + acceptor.set_verify(verify_mode); + + let mut cert_store = x509::store::X509StoreBuilder::new().context("error creating ca certificate store")?; + match tls_ca_cert { + CaCertificate::System => { + return Err(failure::format_err!("cannot use system ca certificates for client certificate validation")); + } + CaCertificate::Custom { x509: tls_ca_cert_x509 } => { + cert_store.add_cert(tls_ca_cert_x509).context("error adding custom ca certificate")?; + } + } + acceptor.set_verify_cert_store(cert_store.build()).context("error setting ca certificate store")?; + + Ok(Self { + acceptor: acceptor.build(), + }) + } + + pub fn accept(&self, stream: T) -> Result, HandshakeError> { + self.acceptor.accept(stream) + .map(TlsStream::new) + .map_err(HandshakeError::from) + } +} + +// +// TlsConnector impls +// + +impl TlsConnector { + pub fn new(maybe_tls_identity: Option, + tls_hostname: TlsHostname, + tls_ca_certs: Vec) + -> Result + { + let mut connector = ssl::SslConnector::builder_no_default_verify_paths(ssl::SslMethod::tls()).context("error creating connector")?; + if let Some(tls_identity) = maybe_tls_identity { + connector.set_private_key(&tls_identity.pkcs12.pkey).context("error setting client private key")?; + connector.set_certificate(&tls_identity.pkcs12.cert).context("error setting client certificate")?; + if let Some(chain) = tls_identity.pkcs12.chain { + for cert in chain.iter().rev() { + connector.add_extra_chain_cert(cert.to_owned()).context("error adding client certificate chain")?; + } + } + } + + connector.set_min_proto_version(Some(ssl::SslVersion::TLS1_2)).context("error setting minimum tls version")?; + connector.set_max_proto_version(Some(ssl::SslVersion::TLS1_2)).context("error setting maximum tls version")?; + + let mut cert_store = x509::store::X509StoreBuilder::new().context("error creating ca certificate store")?; + for tls_ca_cert in tls_ca_certs { + match tls_ca_cert { + CaCertificate::System => { + add_system_ca_certificates(&mut cert_store).context("error adding system ca certificates")?; + } + CaCertificate::Custom { x509: tls_ca_cert_x509 } => { + cert_store.add_cert(tls_ca_cert_x509).context("error adding custom ca certificate")?; + } + } + } + connector.set_verify_cert_store(cert_store.build()).context("error setting ca certificate store")?; + + Ok(TlsConnector { + connector: connector.build(), + hostname: tls_hostname, + }) + } + + pub fn connect(&self, stream: T) -> Result, HandshakeError> { + let mut connect_config = self.connector.configure().map_err(|error| HandshakeError::Failure(HandshakeFailure::SetupError(error)))?; + + let hostname = match &self.hostname { + TlsHostname::Hostname(hostname) => &hostname, + TlsHostname::AcceptInvalid => { + connect_config.set_verify_hostname(false); + connect_config.set_use_server_name_indication(false); + "" + } + }; + + connect_config.connect(hostname, stream) + .map(TlsStream::new) + .map_err(HandshakeError::from) + } +} + +// +// Hostname impls +// + +impl TlsHostname { + pub fn new(hostname: String) -> Self { + Self::Hostname(hostname) + } +} + +// +// Identity impls +// + +impl Identity { + pub fn from_pkcs12_file(path: &Path, password: &str) -> Result { + let mut file = File::open(path)?; + let file_len = file.metadata()?.len() as usize; + let mut data = clear_on_drop::ClearOnDrop::new(vec![0; file_len].into_boxed_slice()); + + file.read_exact(data.as_mut())?; + + Self::from_pkcs12(&data, password) + } + pub fn from_pkcs12(data: &[u8], password: &str) -> Result { + let pkcs12 = pkcs12::Pkcs12::from_der(data)?.parse(password)?; + Ok(Self { pkcs12 }) + } +} + +// +// CaCertificate impls +// + +impl CaCertificate { + pub fn from_pem(data: &[u8]) -> Result { + match x509::X509::from_pem(data) { + Ok(x509) => Ok(Self::Custom { x509 }), + Err(error) => Err(failure::Error::from(error)), + } + } +} + +// +// MidHandshakeTlsStream +// + +impl MidHandshakeTlsStream { + pub fn handshake(self) -> Result, HandshakeError> { + self.stream.handshake() + .map(TlsStream::new) + .map_err(HandshakeError::from) + } +} + +impl AsRawFd for MidHandshakeTlsStream { + fn as_raw_fd(&self) -> RawFd { + self.stream.get_ref().as_raw_fd() + } +} + +// +// TlsStream impls +// + +impl TlsStream { + fn new(stream: ssl::SslStream) -> Self { + Self { stream } + } +} + +impl ProxyRead for TlsStream { + fn read(&mut self, buf: &mut [u8]) -> Result { + match self.stream.ssl_read(buf) { + Err(ref error) if error.code() == ssl::ErrorCode::ZERO_RETURN => Ok(0), + result => openssl_to_proxy_stream_result(result), + } + } +} + +impl ProxyWrite for TlsStream { + fn write(&mut self, buf: &[u8]) -> Result { + openssl_to_proxy_stream_result(self.stream.ssl_write(buf)) + } + fn shutdown(&mut self) -> Result<(), ProxyStreamError> { + openssl_to_proxy_stream_result(self.stream.shutdown().map(drop)) + } +} + +impl AsRawFd for TlsStream { + fn as_raw_fd(&self) -> RawFd { + self.stream.get_ref().as_raw_fd() + } +} + +fn openssl_to_proxy_stream_result(result: Result) -> Result { + match result { + Ok(value) => Ok(value), + Err(ref error) if error.code() == ssl::ErrorCode::WANT_READ => Err(ProxyStreamError::WantRead), + Err(ref error) if error.code() == ssl::ErrorCode::WANT_WRITE => Err(ProxyStreamError::WantWrite), + Err(ref error) if error.code() == ssl::ErrorCode::SYSCALL && error.io_error().is_none() => + Err(ProxyStreamError::Io(io::Error::new(io::ErrorKind::UnexpectedEof, "tcp closed"))), + Err(error) => { + let error = error.into_io_error().unwrap_or_else(|error| io::Error::new(io::ErrorKind::Other, error)); + Err(ProxyStreamError::Io(error)) + } + } +} + +// +// HandshakeError impls +// + +impl From> for HandshakeError { + fn from(error: ssl::HandshakeError) -> Self { + match error { + ssl::HandshakeError::SetupFailure(error) => Self::Failure(HandshakeFailure::SetupError(error)), + ssl::HandshakeError::Failure(stream) => Self::from(stream), + ssl::HandshakeError::WouldBlock(stream) => { + match stream.error().code() { + ssl::ErrorCode::WANT_READ => Self::WantRead(MidHandshakeTlsStream { stream }), + ssl::ErrorCode::WANT_WRITE => Self::WantWrite(MidHandshakeTlsStream { stream }), + _ => unreachable!(), + } + } + } + } +} +impl From> for HandshakeError { + fn from(stream: ssl::MidHandshakeSslStream) -> Self { + let verify_result = stream.ssl().verify_result(); + let error = stream.into_error(); + if verify_result != x509::X509VerifyResult::OK { + Self::Failure(HandshakeFailure::VerifyError(error, verify_result)) + } else { + Self::Failure(HandshakeFailure::OtherError(error)) + } + } +} + +// +// internal +// + +fn add_system_ca_certificates(store: &mut x509::store::X509StoreBuilder) -> Result<(), failure::Error> { + let system_cert_dir = fs::read_dir(Path::new(r"/etc/ssl/certs/")).context("error reading /etc/ssl/certs/")?; + let mut read_files = HashSet::new(); + for dir_entry_result in system_cert_dir { + let dir_entry_path = dir_entry_result.context("error reading /etc/ssl/certs/")?.path(); + match dir_entry_path.canonicalize() { + Ok(canonical_path) => { + if !read_files.contains(&canonical_path) { + match add_certificate_file(store, &canonical_path) { + Ok(()) => (), + Err(error) => { + warn!("error reading system certificate {}: {}", canonical_path.display(), error); + } + } + read_files.insert(canonical_path); + } + } + Err(error) => { + warn!("error reading system certificate {}: {}", dir_entry_path.display(), error); + } + } + } + Ok(()) +} + +fn add_certificate_file(store: &mut x509::store::X509StoreBuilder, path: &Path) -> Result<(), failure::Error> { + if path.is_dir() { + return Ok(()); + } + let data = fs::read(path).context("error reading file")?; + let x509 = x509::X509::from_pem(&data).context("invaild pem data")?; + store.add_cert(x509).context("error adding ca certificate to store")?; + Ok(()) +} diff --git a/service/rustunnel/src/util.rs b/service/rustunnel/src/util.rs new file mode 100644 index 0000000..8dcfbc7 --- /dev/null +++ b/service/rustunnel/src/util.rs @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::io; +use std::os::unix::prelude::*; + +use nix::errno::{Errno}; +use nix::fcntl; +use nix::fcntl::{OFlag}; + +pub fn convert_nix(result: nix::Result) -> io::Result { + match result { + Ok(value) => Ok(value), + Err(nix::Error::Sys(errno)) => Err(errno.into()), + Err(nix::Error::InvalidPath) => Err(Errno::EINVAL.into()), + Err(nix::Error::InvalidUtf8) => Err(Errno::EINVAL.into()), + Err(nix::Error::UnsupportedOperation) => Err(io::Error::new(io::ErrorKind::Other, "unsupported")), + } +} + +pub fn set_nonblocking(fd: RawFd) -> io::Result<()> { + let flags = OFlag::from_bits(convert_nix(fcntl::fcntl(fd, fcntl::F_GETFL))?).unwrap_or_else(OFlag::empty); + assert_eq!(convert_nix(fcntl::fcntl(fd, fcntl::F_SETFL(flags | OFlag::O_NONBLOCK)))?, 0); + Ok(()) +} diff --git a/service/rustunnel/tests/client_child_test.rs b/service/rustunnel/tests/client_child_test.rs new file mode 100644 index 0000000..bd2ebd0 --- /dev/null +++ b/service/rustunnel/tests/client_child_test.rs @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#[macro_use] +mod util; + +use std::net::*; +use std::thread; +use std::sync::*; + +use failure::{ResultExt}; +use native_tls::{Protocol, TlsAcceptor}; +use rustunnel::*; +use rustunnel::stream::*; +use rustunnel::tls::{CaCertificate, TlsHostname}; + +use self::util::*; + +fn main() { + setup_child_common(); + test!(test_valid); + test!(test_accept_invalid); + test!(test_valid_no_hostname); + test!(test_wrong_hostname); + test!(test_wrong_no_hostname); + test!(test_no_ca); + test!(test_wrong_ca_server_certificate); + test!(test_wrong_ca_server_certificate_same_issuer); + test!(test_wrong_system_ca_server_certificate); +} + +fn test_valid() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let ca_pem = test_child.ca.to_pem().expect(error_line!()); + let tls_ca_certs = vec![CaCertificate::from_pem(ca_pem.as_bytes()).expect(error_line!())]; + let tls_hostname = TlsHostname::new("correct_hostname".to_string()); + + let mut client_pipe_stream = test_child.start_child("correct_hostname", tls_hostname, tls_ca_certs) + .expect(error_line!()) + .expect(error_line!()); + + assert_stream_open(&client_pipe_stream).expect(error_line!()); + client_pipe_stream.shutdown().expect(error_line!()); + assert_stream_closed(client_pipe_stream).expect(error_line!()); +} + +fn test_accept_invalid() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let ca_pem = test_child.ca.to_pem().expect(error_line!()); + let tls_ca_certs = vec![CaCertificate::from_pem(ca_pem.as_bytes()).expect(error_line!())]; + let tls_hostname = TlsHostname::AcceptInvalid; + + let mut client_pipe_stream = test_child.start_child("kbuptlsd_test_server", tls_hostname, tls_ca_certs) + .expect(error_line!()) + .expect(error_line!()); + + assert_stream_open(&client_pipe_stream).expect(error_line!()); + client_pipe_stream.shutdown().expect(error_line!()); + assert_stream_closed(client_pipe_stream).expect(error_line!()); +} + +fn test_valid_no_hostname() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let ca_pem = test_child.ca.to_pem().expect(error_line!()); + let tls_ca_certs = vec![CaCertificate::from_pem(ca_pem.as_bytes()).expect(error_line!())]; + let tls_hostname = TlsHostname::AcceptInvalid; + + let mut client_pipe_stream = test_child.start_child("", tls_hostname, tls_ca_certs) + .expect(error_line!()) + .expect(error_line!()); + + assert_stream_open(&client_pipe_stream).expect(error_line!()); + client_pipe_stream.shutdown().expect(error_line!()); + assert_stream_closed(client_pipe_stream).expect(error_line!()); +} + +fn test_wrong_hostname() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let ca_pem = test_child.ca.to_pem().expect(error_line!()); + let tls_ca_certs = vec![CaCertificate::from_pem(ca_pem.as_bytes()).expect(error_line!())]; + let tls_hostname = TlsHostname::new("correct_hostname".to_string()); + + assert!(test_child.start_child("wrong_hostname", tls_hostname, tls_ca_certs).expect(error_line!()).is_err()); +} + +fn test_wrong_no_hostname() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let ca_pem = test_child.ca.to_pem().expect(error_line!()); + let tls_ca_certs = vec![CaCertificate::from_pem(ca_pem.as_bytes()).expect(error_line!())]; + let tls_hostname = TlsHostname::new("correct_hostname".to_string()); + + assert!(test_child.start_child("", tls_hostname, tls_ca_certs).expect(error_line!()).is_err()); +} + +fn test_no_ca() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let tls_ca_certs = vec![]; + let tls_hostname = TlsHostname::AcceptInvalid; + + assert!(test_child.start_child("kbuptlsd_test_server", tls_hostname, tls_ca_certs).expect(error_line!()).is_err()); +} + +fn test_wrong_ca_server_certificate() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let wrong_ca = TestCa::generate("kbuptlsd_test_wrong_ca").expect(error_line!()); + let wrong_ca_pem = wrong_ca.to_pem().expect(error_line!()); + let tls_ca_certs = vec![CaCertificate::from_pem(wrong_ca_pem.as_bytes()).expect(error_line!())]; + let tls_hostname = TlsHostname::AcceptInvalid; + + assert!(test_child.start_child("kbuptlsd_test_server", tls_hostname, tls_ca_certs).expect(error_line!()).is_err()); +} + +fn test_wrong_ca_server_certificate_same_issuer() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let wrong_ca = TestCa::generate("kbuptlsd_test_ca").expect(error_line!()); + let wrong_ca_pem = wrong_ca.to_pem().expect(error_line!()); + let tls_ca_certs = vec![CaCertificate::from_pem(wrong_ca_pem.as_bytes()).expect(error_line!())]; + let tls_hostname = TlsHostname::AcceptInvalid; + + assert!(test_child.start_child("kbuptlsd_test_server", tls_hostname, tls_ca_certs).expect(error_line!()).is_err()); +} + +fn test_wrong_system_ca_server_certificate() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let tls_ca_certs = vec![CaCertificate::System]; + let tls_hostname = TlsHostname::AcceptInvalid; + + assert!(test_child.start_child("kbuptlsd_test_server", tls_hostname, tls_ca_certs).expect(error_line!()).is_err()); +} + +// +// TestChild +// + +struct TestChild { + target_listener: TcpListener, + ca: TestCa, +} + +impl TestChild { + fn new(ca_subject_name: &str) -> Result { + Ok(Self { + target_listener: TcpListener::bind("127.0.0.1:0").context(error_line!())?, + ca: TestCa::generate(ca_subject_name).expect(error_line!()), + }) + } + fn target_addr(&self) -> SocketAddr { + self.target_listener.local_addr().expect(error_line!()) + } + fn start_target(&mut self, subject_name: &str) -> Result>, failure::Error> { + let (tcp_stream, _) = self.target_listener.accept().context(error_line!())?; + let tls_cert = self.ca.generate_signed_certificate(subject_name, true).context(error_line!())?; + let tls_identity = tls_cert.to_native_identity(Some(&self.ca)).context(error_line!())?; + let tls_acceptor = TlsAcceptor::builder(tls_identity).min_protocol_version(Some(Protocol::Tlsv12)) + .max_protocol_version(Some(Protocol::Tlsv12)) + .build() + .context(error_line!())?; + let mut tls_stream = match tls_acceptor.accept(tcp_stream) { + Ok(tls_stream) => tls_stream, + Err(error) => return Ok(Err(error)), + }; + thread::spawn(move || { + stream_echo(&mut tls_stream).expect(error_line!()); + }); + Ok(Ok(())) + } + fn start_child(&mut self, + target_subject_name: &str, + tls_hostname: TlsHostname, + tls_ca_certs: Vec) + -> Result>, failure::Error> + { + let (source_stream_0, source_stream_1) = proxy_pipe_pair()?; + + let target_address = self.target_addr(); + thread::spawn(move || { + let target_tcp_stream = TcpStream::connect(target_address).expect(error_line!()); + let target_tcp_stream = ProxyTcpStream::from_std(target_tcp_stream).expect(error_line!()); + let child = ClientChild::new(tls_hostname, tls_ca_certs, None, source_stream_0, target_tcp_stream).expect(error_line!()); + child.run().expect(error_line!()); + Barrier::new(2).wait(); + }); + + match self.start_target(target_subject_name).context(error_line!())? { + Ok(()) => Ok(Ok(source_stream_1)), + Err(error) => { + assert_stream_closed(source_stream_1).context(error_line!())?; + Ok(Err(error)) + } + } + } +} diff --git a/service/rustunnel/tests/server_child_test.rs b/service/rustunnel/tests/server_child_test.rs new file mode 100644 index 0000000..cffc9e2 --- /dev/null +++ b/service/rustunnel/tests/server_child_test.rs @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#[macro_use] +mod util; + +use std::net::*; +use std::thread; +use std::sync::*; + +use failure::{ResultExt}; +use native_tls::{Protocol, TlsConnector}; +use rustunnel::*; +use rustunnel::stream::{ProxyTcpStream}; + +use self::util::*; + +fn main() { + setup_child_common(); + test!(test_valid); + test!(test_valid_no_chain); + test!(test_no_client_certificate); + test!(test_self_signed_client_certificate); + test!(test_wrong_ca_client_certificate); + test!(test_wrong_ca_client_certificate_same_issuer); +} + +fn test_valid() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let client_tcp_stream = test_child.start_child("kbuptlsd_test_server").expect(error_line!()); + let client_tls_connector = build_client(&mut test_child.ca, true).expect(error_line!()); + let mut client_tls_stream = client_tls_connector.connect("kbuptlsd_test_server", client_tcp_stream).expect(error_line!()); + + assert_stream_open(&mut client_tls_stream).expect(error_line!()); + client_tls_stream.shutdown().expect(error_line!()); + assert_stream_closed(client_tls_stream).expect(error_line!()); +} + +fn test_valid_no_chain() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let client_tcp_stream = test_child.start_child("kbuptlsd_test_server").expect(error_line!()); + let client_tls_connector = build_client(&mut test_child.ca, false).expect(error_line!()); + let mut client_tls_stream = client_tls_connector.connect("kbuptlsd_test_server", client_tcp_stream).expect(error_line!()); + + assert_stream_open(&mut client_tls_stream).expect(error_line!()); + client_tls_stream.shutdown().expect(error_line!()); + assert_stream_closed(client_tls_stream).expect(error_line!()); +} + +fn test_no_client_certificate() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let client_tcp_stream = test_child.start_child("kbuptlsd_test_server").expect(error_line!()); + let client_tls_connector = TlsConnector::builder().min_protocol_version(Some(Protocol::Tlsv12)) + .danger_accept_invalid_hostnames(true) + .add_root_certificate(test_child.ca.to_native_tls_certificate().expect(error_line!())) + .build() + .expect(error_line!()); + client_tls_connector.connect("kbuptlsd_test_server", client_tcp_stream).expect_err(error_line!()); +} + +fn test_self_signed_client_certificate() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let client_tcp_stream = test_child.start_child("kbuptlsd_test_server").expect(error_line!()); + + let client_p12_der = generate_self_signed_certificate_pkcs12().expect(error_line!()); + let client_identity = native_tls::Identity::from_pkcs12(&client_p12_der, "").expect(error_line!()); + let client_tls_connector = TlsConnector::builder().identity(client_identity) + .min_protocol_version(Some(Protocol::Tlsv12)) + .danger_accept_invalid_hostnames(true) + .add_root_certificate(test_child.ca.to_native_tls_certificate().expect(error_line!())) + .build() + .expect(error_line!()); + client_tls_connector.connect("kbuptlsd_test_server", client_tcp_stream).expect_err(error_line!()); +} + +fn test_wrong_ca_client_certificate() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let client_tcp_stream = test_child.start_child("kbuptlsd_test_server").expect(error_line!()); + let mut test_wrong_ca = TestCa::generate("kbuptlsd_test_wrong_ca").expect(error_line!()); + let client_tls_connector = build_client(&mut test_wrong_ca, true).expect(error_line!()); + client_tls_connector.connect("kbuptlsd_test_server", client_tcp_stream).expect_err(error_line!()); +} + +fn test_wrong_ca_client_certificate_same_issuer() { + let mut test_child = TestChild::new("kbuptlsd_test_ca").expect(error_line!()); + let client_tcp_stream = test_child.start_child("kbuptlsd_test_server").expect(error_line!()); + let mut test_ca_2 = TestCa::generate("kbuptlsd_test_ca").expect(error_line!()); + let client_tls_connector = build_client(&mut test_ca_2, true).expect(error_line!()); + client_tls_connector.connect("kbuptlsd_test_server", client_tcp_stream).expect_err(error_line!()); +} + +// +// helpers +// + +fn build_client(test_ca: &mut TestCa, valid_chain: bool) -> Result { + let client_cert = test_ca.generate_signed_certificate("kbupdtlsd_test_client", false).expect(error_line!()); + let client_cert_ca = match valid_chain { + true => Some(&*test_ca), + false => None, + }; + let client_cert_p12 = client_cert.to_pkcs12(client_cert_ca).context(error_line!())?; + let client_cert_der = client_cert_p12.to_der().context(error_line!())?; + let client_identity = native_tls::Identity::from_pkcs12(&client_cert_der, "").context(error_line!())?; + let client_connector = TlsConnector::builder().identity(client_identity) + .min_protocol_version(Some(Protocol::Tlsv12)) + .danger_accept_invalid_hostnames(true) + .add_root_certificate(test_ca.to_native_tls_certificate().context(error_line!())?) + .build() + .context(error_line!())?; + Ok(client_connector) +} + +fn generate_self_signed_certificate_pkcs12() -> Result, failure::Error> { + let test_ca = TestCa::generate("kbuptlsd_test_ca").expect(error_line!()); + let test_ca_p12 = test_ca.to_pkcs12().expect(error_line!()); + let test_ca_p12_der = test_ca_p12.to_der().expect(error_line!()); + Ok(test_ca_p12_der) +} + +// +// TestChild +// + +struct TestChild { + source_listener: TcpListener, + ca: TestCa, +} + +impl TestChild { + fn new(ca_subject_name: &str) -> Result { + Ok(Self { + source_listener: TcpListener::bind("127.0.0.1:0").context(error_line!())?, + ca: TestCa::generate(ca_subject_name).expect(error_line!()), + }) + } + fn connect_source(&self) -> Result { + let address = self.source_listener.local_addr().context(error_line!())?; + let connection = TcpStream::connect(address).context(error_line!())?; + Ok(connection) + } + fn start_child(&mut self, subject_name: &str) -> Result { + let identity = self.ca.generate_server_identity(subject_name).context(error_line!())?; + let tls_ca_cert = self.ca.to_rustunnel_tls_certificate().context(error_line!())?; + let source_listener = self.source_listener.try_clone().context(error_line!())?; + let (target_stream_0, + target_stream_1) = util::proxy_pipe_pair().expect(error_line!()); + thread::spawn(move || { + let (source_stream, _) = source_listener.accept().expect(error_line!()); + let source_stream = ProxyTcpStream::from_std(source_stream).expect(error_line!()); + let child = ServerChild::new(tls_ca_cert, identity, source_stream, target_stream_0).expect(error_line!()); + + child.run().expect(error_line!()); + Barrier::new(2).wait(); + }); + + let client_tcp_stream = self.connect_source().expect(error_line!()); + + thread::spawn(move || { + std::io::copy(&mut &target_stream_1, &mut &target_stream_1).expect(error_line!()); + }); + + Ok(client_tcp_stream) + } +} diff --git a/service/rustunnel/tests/util.rs b/service/rustunnel/tests/util.rs new file mode 100644 index 0000000..73328e7 --- /dev/null +++ b/service/rustunnel/tests/util.rs @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2019 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#![allow(dead_code, unused_macros)] + +use std::cell::*; +use std::io; +use std::io::prelude::*; +use std::os::unix::prelude::*; + +use failure::{ResultExt}; +use rustunnel; +use rustunnel::*; +use rustunnel::stream::*; +use rustunnel::util::{convert_nix}; +use nix::fcntl; +use nix::fcntl::OFlag; +use nix::fcntl::FcntlArg::*; +use nix::unistd; +use openssl::asn1::*; +use openssl::bn::*; +use openssl::hash::*; +use openssl::rsa::*; +use openssl::stack::*; +use openssl::pkcs12::*; +use openssl::pkey::*; +use openssl::x509::*; +use openssl::x509::extension::*; +use rand_chacha::{ChaChaRng}; +use rand_core::{RngCore, SeedableRng}; + +pub struct TestCa { + private_key: PKey, + certificate: X509, + next_serial: u32, +} + +pub struct TestCaSignedCertificate { + private_key: PKey, + certificate: X509, +} + +pub struct TestPipeStream { + read_fd: RawFd, + write_fd: Option, +} + +thread_local! { + pub static RAND: RefCell = RefCell::new(SeedableRng::seed_from_u64(0)); +} + +macro_rules! error_line { + () => { concat!(module_path!(), ":", line!()) }; +} + +macro_rules! test { + ($name:ident) => { + let () = $name(); + println!(concat!("test ", stringify!($name), " ... ok")); + }; +} + +pub fn assert_stream_closed(mut tcp_stream: impl Read) -> Result<(), failure::Error> { + let mut buf = [0; 128]; + match tcp_stream.read(&mut buf[..]).context("error reading from socket")? { + 0 => Ok(()), + n => Err(failure::format_err!("socket not closed, read {} bytes: {}", n, String::from_utf8_lossy(&buf[..]))), + } +} + +pub fn assert_stream_open(mut tcp_stream: impl Read + Write) -> Result<(), failure::Error> { + let rand_id = RAND.with(|rand| rand.borrow_mut().next_u64()); + let message = format!("{:016x} it works!\n", rand_id).into_bytes(); + let mut buffer = vec![0; message.len()]; + + tcp_stream.write_all(&message).context("error writing to socket")?; + let () = tcp_stream.read_exact(&mut buffer).context("error reading from socket")?; + if buffer == message { + Ok(()) + } else { + Err(failure::format_err!("mock client output doesnt match: {} != {}", String::from_utf8_lossy(&buffer), String::from_utf8_lossy(&message))) + } +} + +pub fn stream_echo(stream: &mut (impl Read + Write)) -> io::Result<()> { + loop { + let buf_len = 1 + (RAND.with(|rand| rand.borrow_mut().next_u32()) as usize % 64); + let mut buf = vec![0; buf_len]; + let len = match stream.read(&mut buf) { + Ok(0) => break, + Ok(wrote_len) => wrote_len, + Err(ref error) if error.kind() == io::ErrorKind::Interrupted => continue, + Err(error) => return Err(error), + }; + stream.write_all(&buf[..len])?; + } + Ok(()) +} + +pub fn proxy_pipe_pair() -> Result<(ProxyPipeStream, TestPipeStream), failure::Error> { + let (source_pipe_0_rx, source_pipe_0_tx) = unistd::pipe2(OFlag::O_CLOEXEC | OFlag::O_NONBLOCK).context(error_line!())?; + let (source_pipe_1_rx, source_pipe_1_tx) = unistd::pipe2(OFlag::O_CLOEXEC | OFlag::O_NONBLOCK).context(error_line!())?; + + let source_pipe_1_rx_oflags = OFlag::from_bits(fcntl::fcntl(source_pipe_1_rx, F_GETFL).context(error_line!())?).expect(error_line!()); + fcntl::fcntl(source_pipe_1_rx, F_SETFL(source_pipe_1_rx_oflags & !OFlag::O_NONBLOCK)).context(error_line!())?; + + let source_pipe_0_tx_oflags = OFlag::from_bits(fcntl::fcntl(source_pipe_0_tx, F_GETFL).context(error_line!())?).expect(error_line!()); + fcntl::fcntl(source_pipe_0_tx, F_SETFL(source_pipe_0_tx_oflags & !OFlag::O_NONBLOCK)).context(error_line!())?; + + Ok((ProxyPipeStream::new(source_pipe_0_rx, source_pipe_1_tx).expect(error_line!()), + TestPipeStream::new(source_pipe_1_rx, source_pipe_0_tx))) +} + +pub fn setup_child_common() { + seccomp::configure_malloc().expect(error_line!()); + setup_child_logger(); +} + +pub fn setup_child_logger() { + let logger = logger::Logger { level: log::Level::Debug }; + log::set_boxed_logger(Box::new(logger)).expect("logger already set"); + log::set_max_level(log::Level::Debug.to_level_filter()); +} + +// +// TestCa impls +// + +impl TestCa { + pub fn generate(subject_name_str: &str) -> Result { + let private_rsa_key = Rsa::generate(2048).context(error_line!())?; + let private_key = PKey::from_rsa(private_rsa_key).context(error_line!())?; + + let valid_from_time = Asn1Time::days_from_now(0).context(error_line!())?; + let valid_until_time = Asn1Time::days_from_now(1).context(error_line!())?; + + let mut subject_name = X509NameBuilder::new().context(error_line!())?; + let () = subject_name.append_entry_by_text("CN", subject_name_str).context(error_line!())?; + let subject_name = subject_name.build(); + + let basic_constraints = BasicConstraints::new().ca().critical().build().context(error_line!())?; + + let mut certificate = X509Builder::new().context(error_line!())?; + let () = certificate.set_not_before(&valid_from_time).context(error_line!())?; + let () = certificate.set_not_after(&valid_until_time).context(error_line!())?; + let () = certificate.set_issuer_name(&subject_name).context(error_line!())?; + let () = certificate.set_subject_name(&subject_name).context(error_line!())?; + let () = certificate.set_pubkey(&private_key).context(error_line!())?; + let () = certificate.append_extension(basic_constraints).context(error_line!())?; + let () = certificate.sign(&private_key, MessageDigest::sha256()).context(error_line!())?; + let certificate = certificate.build(); + Ok(Self { + private_key, + certificate, + next_serial: 1, + }) + } + + pub fn to_pem(&self) -> Result { + let pem_bytes = self.certificate.to_pem()?; + let pem_string = String::from_utf8(pem_bytes).context(error_line!())?; + Ok(pem_string) + } + + pub fn to_pkcs12(&self) -> Result { + let mut ca = Stack::new().context(error_line!())?; + ca.push(self.certificate.clone()).context(error_line!())?; + + let mut pkcs12 = Pkcs12::builder(); + pkcs12.ca(ca); + let pkcs12 = pkcs12.build("", "kbuptlsd_test_pkcs12", &self.private_key, &self.certificate).context(error_line!())?; + Ok(pkcs12) + } + + pub fn to_native_tls_certificate(&self) -> Result { + let der = self.certificate.to_der().context(error_line!())?; + let certificate = native_tls::Certificate::from_der(&der).context(error_line!())?; + Ok(certificate) + } + + pub fn to_rustunnel_tls_certificate(&self) -> Result { + let pem = self.certificate.to_pem().context(error_line!())?; + let certificate = rustunnel::tls::CaCertificate::from_pem(&pem).context(error_line!())?; + Ok(certificate) + } + + pub fn generate_server_identity(&mut self, subject_name_str: &str) -> Result { + let signed_server_certificate = self.generate_signed_certificate(subject_name_str, true).context(error_line!())?; + let server_certificate_pkcs12 = signed_server_certificate.to_pkcs12(Some(self)).context(error_line!())?; + let server_certificate_pkcs12_der = server_certificate_pkcs12.to_der().context(error_line!())?; + let tls_identity = rustunnel::Identity::from_pkcs12(&server_certificate_pkcs12_der, "").context(error_line!())?; + Ok(tls_identity) + } + + pub fn generate_signed_certificate(&mut self, subject_name_str: &str, is_server: bool) -> Result { + let private_rsa_key = Rsa::generate(2048).context(error_line!())?; + let private_key = PKey::from_rsa(private_rsa_key).context(error_line!())?; + let certificate = self.sign_certificate(&private_key, subject_name_str, is_server).context(error_line!())?; + Ok(TestCaSignedCertificate { + private_key, + certificate, + }) + } + + pub fn sign_certificate(&mut self, pubkey: &PKeyRef, subject_name_str: &str, is_server: bool) -> Result { + let valid_from_time = Asn1Time::days_from_now(0).context(error_line!())?; + let valid_until_time = Asn1Time::days_from_now(1).context(error_line!())?; + + let maybe_subject_name = if !subject_name_str.is_empty() { + let mut subject_name = X509NameBuilder::new().context(error_line!())?; + let () = subject_name.append_entry_by_text("CN", subject_name_str).context(error_line!())?; + Some(subject_name.build()) + } else { + None + }; + + let serial_bn = BigNum::from_u32(self.next_serial).context(error_line!())?; + let serial_asn1 = Asn1Integer::from_bn(&serial_bn).context(error_line!())?; + self.next_serial += 1; + + let extended_key_usage = if is_server { + ExtendedKeyUsage::new().critical().server_auth().build().context(error_line!())? + } else { + ExtendedKeyUsage::new().critical().client_auth().build().context(error_line!())? + }; + + let mut certificate = X509Builder::new().context(error_line!())?; + let () = certificate.set_issuer_name(self.certificate.subject_name()).context(error_line!())?; + let () = certificate.set_not_before(&valid_from_time).context(error_line!())?; + let () = certificate.set_not_after(&valid_until_time).context(error_line!())?; + if let Some(subject_name) = maybe_subject_name { + let () = certificate.set_subject_name(&subject_name).context(error_line!())?; + } + if is_server && !subject_name_str.is_empty() { + let x509v3_context = certificate.x509v3_context(Some(&self.certificate), None); + let subject_alt_name = SubjectAlternativeName::new().dns(subject_name_str) + .build(&x509v3_context) + .context(error_line!())?; + let () = certificate.append_extension(subject_alt_name).context(error_line!())?; + } + let () = certificate.set_serial_number(&serial_asn1).context(error_line!())?; + let () = certificate.set_pubkey(pubkey).context(error_line!())?; + let () = certificate.append_extension(extended_key_usage).context(error_line!())?; + let () = certificate.sign(&self.private_key, MessageDigest::sha256()).context(error_line!())?; + Ok(certificate.build()) + } +} + +// +// TestCaSignedCertificate impls +// + +impl TestCaSignedCertificate { + pub fn to_pkcs12(&self, maybe_ca: Option<&TestCa>) -> Result { + let mut ca_stack = Stack::new().context(error_line!())?; + if let Some(ca) = maybe_ca { + ca_stack.push(ca.certificate.clone()).context(error_line!())?; + } + + let mut pkcs12 = Pkcs12::builder(); + pkcs12.ca(ca_stack); + let pkcs12 = pkcs12.build("", "kbuptlsd_test_pkcs12", &self.private_key, &self.certificate).context(error_line!())?; + Ok(pkcs12) + } + + pub fn to_native_identity(&self, maybe_ca: Option<&TestCa>) -> Result { + let pkcs12 = self.to_pkcs12(maybe_ca).context(error_line!())?; + let der = pkcs12.to_der().context(error_line!())?; + let identity = native_tls::Identity::from_pkcs12(&der, "").context(error_line!())?; + Ok(identity) + } +} + +// +// TestPipeStream impls +// + +impl TestPipeStream { + pub fn new(read_fd: RawFd, write_fd: RawFd) -> Self { + Self { + read_fd, + write_fd: Some(write_fd), + } + } + pub fn shutdown(&mut self) -> io::Result<()> { + if let Some(write_fd) = self.write_fd.take() { + convert_nix(unistd::close(write_fd)) + } else { + Ok(()) + } + } +} + +impl Read for TestPipeStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + <&Self>::read(&mut &*self, buf) + } +} +impl Write for TestPipeStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + <&Self>::write(&mut &*self, buf) + } + fn flush(&mut self) -> io::Result<()> { + <&Self>::flush(&mut &*self) + } +} + +impl Read for &'_ TestPipeStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + convert_nix(unistd::read(self.read_fd, buf)) + } +} + +impl Write for &'_ TestPipeStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self.write_fd { + Some(write_fd) => convert_nix(unistd::write(write_fd, buf)), + None => Err(io::ErrorKind::NotConnected.into()), + } + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Drop for TestPipeStream { + fn drop(&mut self) { + if let Some(write_fd) = self.write_fd { + let _ignore = unistd::close(write_fd); + } + let _ignore = unistd::close(self.read_fd); + } +} diff --git a/service/test/azure_nat_timeout.yml b/service/test/azure_nat_timeout.yml new file mode 100644 index 0000000..fbef365 --- /dev/null +++ b/service/test/azure_nat_timeout.yml @@ -0,0 +1,8 @@ +config: + loop: true +steps: + - sleep_ms: 90000 + - adddrop: + src: 'FE' + dst: 'leader' + single_conn: 'true' diff --git a/service/test/client_test.py b/service/test/client_test.py new file mode 100755 index 0000000..8d9e347 --- /dev/null +++ b/service/test/client_test.py @@ -0,0 +1,664 @@ +#!/usr/bin/env python3 + +import os, signal, atexit, random, time, threading, unittest + +from util import eprint, gprint, backup_id_to_str, random_id +from kbupd import Kbupd +from partition import Partition +from peer_ca import PeerCa +from kbupdclient import KbupdClient +from netem import start_playback + +class NetemTestCase(unittest.TestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.ca = PeerCa() + + scripts = os.environ.get('SCRIPT', '').split(',') + cls.scripts = scripts if scripts != [''] else [] + if len(cls.scripts) > 0: + cls.netem_threads = start_playback(*cls.scripts) + + @classmethod + def tearDownClass(cls): + if hasattr(cls, 'netem_threads'): + for thread in cls.netem_threads: + thread.exit.set() + thread.join() + cls.netem_threads = None + + super().tearDownClass() + +def send_valid_requests(test): + client = test.client + + for backup_id in test.backup_ids: + # test backup valid_from checking + client.request(r"status=NotYetValid", "backup", + backup_id, test.backup_pin, test.backup_data, 2, valid_from=2**64-1) + # test backup and restore + client.request(r"status=Ok", "backup", + backup_id, test.backup_pin, test.backup_data, 2) + token = client.request(r"status=Ok", "restore", + backup_id, test.backup_pin) + # test pin mismatch + client.request(r"status=PinMismatch", "restore", + backup_id, random_id(32), token=token) + # test restore with token reuse + client.request(r"status=TokenMismatch", "restore", + backup_id, test.backup_pin, token=token) + # test restore with random token + client.request(r"status=Missing", "restore", + backup_id, test.backup_pin, token=random_id(32)) + # test restore with creation_token reuse + token = client.request(r"status=TokenMismatch", "restore", + backup_id, test.backup_pin, token=token[:32] + random_id(16)) + # test restore valid_from checking + client.request(r"status=NotYetValid", "restore", + backup_id, test.backup_pin, token=token, valid_from=2**64-1) + # test restore after above tries decrement + client.request(r"status=Ok", "restore", + backup_id, test.backup_pin, token=token) + # test restore token mismatch + client.request(r"status=TokenMismatch", "restore", + backup_id, test.backup_pin, token=token) + # test deletion on tries=0 + client.request(r"status=Missing", "restore", + backup_id, random_id(32)) + # test deletion persistence + client.request(r"status=Missing", "restore", + backup_id, test.backup_pin) + +class KbupdTestCase(NetemTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.partitions = [] + cls.enclave_name = "test" + + partition = Partition(cls.ca) + cls.partitions.append(partition) + gprint("Started service %s" % partition.service_id) + gprint("Started partition %s" % partition.get_spec()) + gprint() + + partition = partition.split_partition() + partition.start_partition() + cls.partitions.append(partition) + gprint("Started service %s" % partition.service_id) + gprint("Started 2nd partition %s" % partition.get_spec()) + gprint() + cls.partitions[0].wait_partition_started_source() + cls.partitions[0].resume_partition() + cls.partitions[0].pause_partition() + cls.partitions[0].resume_partition() + cls.partitions[0].wait_partition_source() + partition.wait_partition_destination() + partition.finish_partition() + cls.partitions[0].finish_partition() + + cls.frontend = Kbupd(1337, "frontend", cls.ca, + "--enclave-name", cls.enclave_name, + "--partitions", ';'.join([ p.get_spec() for p in cls.partitions ])) + gprint("Started frontend %s" % cls.frontend.node_id) + gprint() + + cls.backup_ids = (backup_id_to_str(0), + backup_id_to_str(2**256-1), + backup_id_to_str((2**256-1)//2), + backup_id_to_str((2**256-1)//2+1), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)),) + cls.backup_data = backup_id_to_str(random.randint(0, 2**256-1)) + cls.backup_pin = random_id(32) + + cls.client = KbupdClient(cls.frontend, cls.enclave_name, + cls.partitions[0].service_id) + + @classmethod + def tearDownClass(cls): + for p in cls.partitions: + p.kill() + cls.frontend.kill() + + super().tearDownClass() + + def test_00_invalid_requests(self): + client = self.client + service_id = self.partitions[0].service_id + + bad_input = r"ControlErrorSignal" + client.request(bad_input, "backup", + "", self.backup_pin, self.backup_data, 1, token=random_id(32)) + client.request(bad_input, "backup", + random_id(31), self.backup_pin, self.backup_data, 1, + token=random_id(32)) + client.request(bad_input, "backup", + random_id(33), self.backup_pin, self.backup_data, 1, + token=random_id(32)) + for backup_id in self.backup_ids: + client.request(bad_input, "backup", + backup_id, self.backup_pin, "", 1, token=random_id(32)) + client.request(bad_input, "backup", + backup_id, self.backup_pin, random_id(33), 1, + token=random_id(32)) + client.request(bad_input, "backup", + backup_id, "", self.backup_data, 1,token=random_id(32)) + client.request(bad_input, "backup", + backup_id, random_id(33), self.backup_data, 1, + token=random_id(32)) + client.request(bad_input, "backup", + backup_id, self.backup_pin, self.backup_data, 1, + service_id="", token=random_id(32)) + client.request(bad_input, "restore", "", self.backup_pin, token=random_id(32)) + client.request(bad_input, "restore", random_id(31), self.backup_pin, + token=random_id(32)) + client.request(bad_input, "restore", random_id(33), self.backup_pin, + token=random_id(32)) + client.request(bad_input, "restore", backup_id, "", token=random_id(32)) + client.request(bad_input, "restore", backup_id, random_id(33), + token=random_id(32)) + client.request(bad_input, "restore", + backup_id, self.backup_pin, service_id="", + token=random_id(32)) + client.request(bad_input, "backup", + backup_id, self.backup_pin, self.backup_data, 1, + service_id=service_id[:62], + token=random_id(32)) + client.request(bad_input, "backup", + backup_id, self.backup_pin, self.backup_data, 1, + service_id=service_id + "00", + token=random_id(32)) + client.request(bad_input, "backup", + backup_id, self.backup_pin, self.backup_data, 1, + token="") + client.request(bad_input, "backup", + backup_id, self.backup_pin, self.backup_data, 1, + token=random_id(33)) + client.request(bad_input, "restore", backup_id, self.backup_pin, + token="") + client.request(bad_input, "restore", backup_id, self.backup_pin, + token=random_id(33)) + client.request(r"request canceled by enclave", "backup", + backup_id, self.backup_pin, self.backup_data, 1, + service_id=random_id(32), token=random_id(32)) + + def test_00_valid_requests(self): + client = self.client + + for backup_id in self.backup_ids: + client.request(r"status=Missing", "restore", + backup_id, self.backup_pin, token=random_id(32)) + + send_valid_requests(self) + + def test_10_reconnect(self): + client = self.client + backup_id = backup_id_to_str(random.randint(0, 2**256-1)) + + client.request(r"status=Ok", "backup", + backup_id, self.backup_pin, self.backup_data, 1) + client.request(r"status=Ok", "restore", + backup_id, self.backup_pin) + + for partition in self.partitions: + for peer in partition.peers: + for other_partition in self.partitions: + for other_peer in other_partition.peers: + peer.disconnect_peer(other_peer.node_id) + peer.reconnect_peer(other_peer.node_id, "127.0.0.1:%s" % other_peer.peer_port) + + client.request(r"status=Ok", "restore", + backup_id, self.backup_pin) + + def test_20_partitioning_split(self): + self.do_test_partitioning(False, False) + def test_21_partitioning_move_update_specs(self): + self.do_test_partitioning(True, True) + def test_22_partitioning_move(self): + self.do_test_partitioning(False, True) + + def do_test_partitioning(self, update_specs, move): + client = self.client + + client.request(r"status=Ok", "backup", count=100) + + backup_ids = list(self.backup_ids) + if not move: + backup_ids.append(backup_id_to_str((2**256-1)//4)) + backup_ids.append(backup_id_to_str((2**256-1)//4+1)) + + for backup_id in backup_ids: + client.request(r"status=Ok", "backup", + backup_id, self.backup_pin, self.backup_data, 2) + + pre_partition_count = sum([ i.get_backup_count() for i in self.partitions ]) + self.assertIsNotNone(pre_partition_count) + pre_partition_specs = [ p.get_spec() for p in self.partitions ] + + if move: + partition = self.partitions[len(self.partitions)-1].move_partition() + else: + partition = self.partitions[len(self.partitions)-1].split_partition() + self.partitions.append(partition) + gprint("Started service %s" % partition.service_id) + gprint("Started 3rd partition %s" % partition.get_spec()) + gprint() + + partition_specs = pre_partition_specs + [partition.get_spec_no_range()] + + KbupdTestCase.frontend.kill() + KbupdTestCase.frontend = Kbupd(1337, "frontend", self.ca, + "--enclave-name", self.enclave_name, + "--partitions", ';'.join(partition_specs), + append_log=True) + gprint("Started frontend %s" % KbupdTestCase.frontend.node_id) + gprint() + + partition.start_partition() + + self.partitions[len(self.partitions)-2].wait_partition_started_source() + + for backup_id in backup_ids: + token = client.request(r"status=Ok", "restore", + backup_id, self.backup_pin) + + self.partitions[len(self.partitions)-2].resume_partition() + + if update_specs: + self.partitions[len(self.partitions)-2].wait_partition_source() + partition.wait_partition_destination() + partition.finish_partition() + self.partitions[len(self.partitions)-2].finish_partition() + self.assertEqual(pre_partition_count, sum([ i.get_backup_count() for i in self.partitions ])) + if move: + self.partitions[len(self.partitions)-2].kill() + del(self.partitions[len(self.partitions)-2]) + + KbupdTestCase.frontend.kill() + KbupdTestCase.frontend = Kbupd(1337, "frontend", self.ca, + "--enclave-name", self.enclave_name, + "--partitions", ';'.join([ p.get_spec() for p in self.partitions ]), + append_log = True) + gprint("Started frontend %s" % KbupdTestCase.frontend.node_id) + gprint() + + for backup_id in backup_ids: + token = client.request(r"status=Ok", "restore", + backup_id, self.backup_pin) + for backup_id in backup_ids: + token = client.request(r"status=PinMismatch", "restore", + backup_id, random_id(32)) + token = client.request(r"status=Missing", "restore", + backup_id, random_id(32)) + for backup_id in backup_ids: + client.request(r"status=Missing", "restore", + backup_id, self.backup_pin) + + if not update_specs: + self.partitions[len(self.partitions)-2].wait_partition_source() + partition.wait_partition_destination() + partition.finish_partition() + self.partitions[len(self.partitions)-2].finish_partition() + self.assertEqual(pre_partition_count - len(backup_ids), + sum([ i.get_backup_count() for i in self.partitions ])) + if move: + self.partitions[len(self.partitions)-2].kill() + del(self.partitions[len(self.partitions)-2]) + KbupdTestCase.frontend.kill() + KbupdTestCase.frontend = Kbupd(1337, "frontend", self.ca, + "--enclave-name", self.enclave_name, + "--partitions", ';'.join([ p.get_spec() for p in self.partitions ]), + append_log = True) + gprint("Started frontend %s" % KbupdTestCase.frontend.node_id) + gprint() + + def test_30_leader_change(self): + client = self.client + backup_ids = list(self.backup_ids) + backup_ids.append(backup_id_to_str((2**256-1)//4)) + backup_ids.append(backup_id_to_str((2**256-1)//4+1)) + backup_ids.append(backup_id_to_str(random.randint(0, 2**256-1))) + backup_ids.append(backup_id_to_str(random.randint(0, 2**256-1))) + + for backup_id in backup_ids: + client.request(r"status=Ok", "backup", + backup_id, self.backup_pin, self.backup_data, 1) + client.request(r"status=Ok", "restore", + backup_id, self.backup_pin) + + for partition in self.partitions: + while True: + leader= partition.get_replicas()[0] + if leader: + break + time.sleep(1) + leader.kill() + + for backup_id in backup_ids: + client.request(r"status=Ok", "restore", + backup_id, self.backup_pin) + +class KbupdFullStorageTestCase(NetemTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.enclave_name = "test" + cls.partition = Partition(cls.ca, + replicas = 3, + config_file = "replica.benchmark.yml", + debug = False, + storage_size = 10) + gprint("Started service %s" % cls.partition.service_id) + gprint("Started partition %s" % cls.partition.get_spec()) + gprint() + + cls.frontend = Kbupd(1337, "frontend", cls.ca, + "--enclave-name", cls.enclave_name, + "--partitions", cls.partition.get_spec()) + + gprint("Started frontend %s" % cls.frontend.node_id) + gprint() + + cls.client = KbupdClient(cls.frontend, cls.enclave_name, cls.partition.service_id) + + cls.backup_ids = (backup_id_to_str(0), + backup_id_to_str(2**256-1), + backup_id_to_str((2**256-1)//2), + backup_id_to_str((2**256-1)//2+1), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)),) + cls.backup_data = backup_id_to_str(random.randint(0, 2**256-1)) + cls.backup_pin = random_id(32) + + @classmethod + def tearDownClass(cls): + cls.partition.kill() + cls.frontend.kill() + + super().tearDownClass() + + def test_10_storage_size(self): + client = self.client + + for iteration in range(0, 2): + with self.subTest(iteration = iteration): + send_valid_requests(self) + for backup_id in self.backup_ids: + client.request(r"status=Ok", "backup", + backup_id, self.backup_pin, self.backup_data) + + backup_ids_2 = [] + + backup_id = backup_id_to_str(random.randint(0, 2**256-1)) + client.request(r"request canceled by enclave", "backup", + backup_id, self.backup_pin, self.backup_data) + + client.request(r"", "delete", self.backup_ids[0]) + + client.request(r"status=Ok", "backup", + backup_id, self.backup_pin, self.backup_data) + client.request(r"status=Ok", "restore", + backup_id, self.backup_pin) + backup_ids_2.append(backup_id) + + backup_id = backup_id_to_str(random.randint(0, 2**256-1)) + client.request(r"request canceled by enclave", "backup", + backup_id, self.backup_pin, self.backup_data) + + for backup_id in self.backup_ids: + client.request(r"", "delete", backup_id) + + for iteration in range(0, 9): + with self.subTest(iteration = iteration): + backup_id = backup_id_to_str(random.randint(0, 2**256-1)) + client.request(r"status=Ok", "backup", + backup_id, self.backup_pin, self.backup_data) + backup_ids_2.append(backup_id) + + backup_id = backup_id_to_str(random.randint(0, 2**256-1)) + client.request(r"request canceled by enclave", "backup", + backup_id, self.backup_pin, self.backup_data) + + for backup_id in backup_ids_2: + client.request(r"status=Ok", "restore", + backup_id, self.backup_pin) + +class KbupdBenchmarkTestCase(NetemTestCase): + @classmethod + def setUpClass(cls, num_replicas=3): + super().setUpClass() + + cls.enclave_name = "test" + cls.partition = Partition(cls.ca, + replicas = num_replicas, + config_file = "replica.benchmark.yml", + debug = False) + gprint("Started service %s" % cls.partition.service_id) + gprint("Started partition %s" % cls.partition.get_spec()) + gprint() + + cls.frontends = [] + frontend_count = 1 + for port in range(1337, 1337 + 2 * frontend_count, 2): + cls.frontends.append(cls.start_frontend(port)) + + cls.client = KbupdClient(cls.frontends[0], cls.enclave_name, cls.partition.service_id) + + cls.backup_ids = (backup_id_to_str(0), + backup_id_to_str(2**256-1), + backup_id_to_str((2**256-1)//2), + backup_id_to_str((2**256-1)//2+1), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)), + backup_id_to_str(random.randint(0, 2**256-1)),) + cls.backup_data = backup_id_to_str(random.randint(0, 2**256-1)) + cls.backup_pin = random_id(32) + + cls.request_count = 10000 + cls.backup_count = 0 + + @classmethod + def start_frontend(cls, port): + frontend = Kbupd(port, "frontend", cls.ca, + "--enclave-name", cls.enclave_name, + "--partitions", cls.partition.get_spec(), + config_file = "frontend.benchmark.yml", + debug = False) + gprint("Started frontend %s" % frontend.node_id) + gprint() + return frontend + + @classmethod + def tearDownClass(cls): + cls.partition.kill() + for frontend in cls.frontends: + frontend.kill() + + super().tearDownClass() + + def _about_to_xfer(self): + pass + + def test_00_benchmark(self, max_time=30): + cls = self.__class__ + + cmd = ["client", "--enclave-name", self.enclave_name, "backup", "--request-count", str(self.request_count)] + backup_count = 0 + start_time = time.time() + elapsed_time = 0 + while elapsed_time < max_time: + kbupctls = [] + for frontend in self.frontends: + self.client.log.write("CMD: " + " ".join(cmd) + "\n") + self.client.log.flush() + kbupctls.append(frontend.kbupctl_async(*cmd)) + for kbupctl in kbupctls: + kbupctl_res = kbupctl.wait() + if kbupctl_res != 0: + eprint() + eprint("TEST '%s' FAILED, returned %d" % (" ".join(cmd), kbupctl_res)) + raise Exception("Test failed") + elapsed_time = time.time() - start_time + backup_count += self.request_count * len(self.frontends) + gprint("performed %7d backups in %6.03fs (%4d/s)" % (backup_count, elapsed_time, backup_count / elapsed_time)) + cls.backup_count += backup_count + gprint() + + def test_10_partition_transfer(self, num_keys=10**6): + cls = self.__class__ + + for backup_id in self.backup_ids: + self.client.request(r"status=Ok", "backup", backup_id, self.backup_pin, self.backup_data, 1) + cls.backup_count += 1 + + backup_count = 0 + start_time = time.time() + elapsed_time = 0 + request_count = 50000 + cmd = ["client", "--enclave-name", self.enclave_name, "create", + "--request-count", str(request_count), "--max-parallel", "5000"] + while backup_count < num_keys: + kbupctls = [] + for frontend in self.frontends: + self.client.log.write("CMD: " + " ".join(cmd) + "\n") + self.client.log.flush() + kbupctls.append(frontend.kbupctl_async(*cmd)) + for kbupctl in kbupctls: + kbupctl_res = kbupctl.wait() + if kbupctl_res != 0: + eprint() + eprint("TEST '%s' FAILED, returned %d" % (" ".join(cmd), kbupctl_res)) + raise Exception("Test failed") + elapsed_time = time.time() - start_time + backup_count += request_count * len(self.frontends) + gprint("created %7d backups in %6.03fs (%4d/s)" % (backup_count, elapsed_time, backup_count / elapsed_time)) + cls.backup_count += backup_count + + self.assertEqual(self.partition.get_backup_count(), cls.backup_count) + + for frontend in cls.frontends: + frontend.kill() + + self.new_partition = self.partition.move_partition() + + gprint("Started service %s" % self.new_partition.service_id) + gprint("Started 2nd partition %s" % self.new_partition.get_spec()) + gprint() + + self.new_partition.start_partition() + self.partition.wait_partition_started_source() + self.partition.resume_partition() + self._about_to_xfer() + start_time = time.time() + + self.partition.wait_partition_source() + self.new_partition.wait_partition_destination() + elapsed_time = time.time() - start_time + + gprint("transferred %d backups in %.03fs (%4d/s)" % (self.backup_count, elapsed_time, self.backup_count / elapsed_time)) + gprint() + + self.new_partition.finish_partition() + self.partition.finish_partition() + self.partition.kill() + cls.partition = self.new_partition + del(self.new_partition) + + new_frontends = [] + for frontend in cls.frontends: + new_frontends.append(self.start_frontend(frontend.control_port)) + cls.frontends = new_frontends + + for backup_id in self.backup_ids: + self.client.request(r"status=Ok", "restore", backup_id, self.backup_pin) + + self.assertEqual(self.partition.get_backup_count(), cls.backup_count) + +class KbupdXferFailureTestCase(KbupdBenchmarkTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass(num_replicas=5) + + def _cause_havoc(self): + cls = self.__class__ + + time.sleep(5) + + start = time.monotonic() + while True: + leader, followers = self.new_partition.get_replicas() + if leader: + break + time.sleep(1) + + #Kill leader + self.new_partition.kill(leader) + gprint("Killed dest partition leader", flush=True) + + now = time.monotonic() + if now < start + 5: + time.sleep(5 - (now - start)) + + leader, followers = cls.partition.get_replicas() + + #Kill follower + cls.partition.kill(followers[0]) + gprint("Killed source partition follower", flush=True) + + if now < start + 10: + time.sleep(10 - (now - start)) + + while not leader: + leader, followers = cls.partition.get_replicas() + time.sleep(1) + + #Kill leader + cls.partition.kill(leader) + gprint("Killed source partition leader", flush=True) + + def _about_to_xfer(self): + leader, followers = self.new_partition.get_replicas() + #Kill follower + self.new_partition.kill(followers[0]) + gprint("Killed dest partition follower", flush=True) + + self.thread = threading.Thread(target=self._cause_havoc, name='havoc') + self.thread.start() + + def test_00_benchmark(self): + super().test_00_benchmark(max_time=15) + + def test_10_partition_transfer(self): + super().test_10_partition_transfer(num_keys=3*10**5) + assert(not self.thread.is_alive()) + self.thread.join() + +def kill_all(*args): + Kbupd.kill_all() + raise(Exception("SIGTERM")) + +def cleanup(): + if len(Kbupd.processes) > 0: + Kbupd.kill_all() + +if __name__ == "__main__": + signal.signal(signal.SIGTERM, kill_all) + atexit.register(cleanup) + unittest.installHandler() + unittest.main(failfast=True) diff --git a/service/test/fail_attestation.yml b/service/test/fail_attestation.yml new file mode 100644 index 0000000..39f1518 --- /dev/null +++ b/service/test/fail_attestation.yml @@ -0,0 +1,11 @@ +config: + loop: true +steps: + - sleep_ms: 15000 + - droptohost: + host: 'test-as.sgx.trustedservices.intel.com' + - randtcpkill: + num_replicas: 0 + - sleep_ms: 60000 + - flushdrop: + - sleep_ms: 30000 diff --git a/service/test/fail_attestation_cert.yml b/service/test/fail_attestation_cert.yml new file mode 100644 index 0000000..3f2881f --- /dev/null +++ b/service/test/fail_attestation_cert.yml @@ -0,0 +1,12 @@ +config: + loop: true +steps: + - sleep_ms: 15000 + - rewritedst: + old_dst: 'test-as.sgx.trustedservices.intel.com' + new_dst: 'www.google.com' + - randtcpkill: + num_replicas: 0 + - sleep_ms: 60000 + - flushrewrite: + - sleep_ms: 30000 diff --git a/service/test/fail_attestation_rst.yml b/service/test/fail_attestation_rst.yml new file mode 100644 index 0000000..f53c990 --- /dev/null +++ b/service/test/fail_attestation_rst.yml @@ -0,0 +1,12 @@ +config: + loop: true +steps: + - sleep_ms: 15000 + - droptohost: + host: 'test-as.sgx.trustedservices.intel.com' + send_rst: 'true' + - randtcpkill: + num_replicas: 0 + - sleep_ms: 60000 + - flushdrop: + - sleep_ms: 30000 diff --git a/service/test/fe_no_leaders.yml b/service/test/fe_no_leaders.yml new file mode 100644 index 0000000..04d6498 --- /dev/null +++ b/service/test/fe_no_leaders.yml @@ -0,0 +1,15 @@ +config: + loop: true +steps: + - sleep_ms: 15000 + - adddrop: + src: 'FE' + dst: 'leader' + - randtcpkill: + num_replicas: 0 + - sleep_ms: 2000 + - randtcpkill: + num_replicas: 0 + - sleep_ms: 100000 + - flushdrop: + - sleep_ms: 300000 diff --git a/service/test/kbupd.py b/service/test/kbupd.py new file mode 100644 index 0000000..d081b60 --- /dev/null +++ b/service/test/kbupd.py @@ -0,0 +1,219 @@ +import os, subprocess, tempfile, time, re, signal + +from util import DIM, CLEAR, create_cgroup + +class Kbupd(): + processes = [] + frontends = [] + + def __init__(self, port, subcommand, ca, *args, **kwargs): + for bd in (os.getenv("BIN_DIR"), ".", "build/target/debug"): + if bd == None: continue + if os.path.isfile(os.path.join(bd, "kbupd")): + self.kbupd_bin = os.path.join(bd, "kbupd") + self.kbupctl_bin = os.path.join(bd, "kbupctl") + self.kbuptlsd_bin = os.path.join(bd, "kbuptlsd") + break + for bd in (os.getenv("CONFIG_DIR"), ".", "config"): + if bd == None: continue + if os.path.isfile(os.path.join(bd, "replica.client_test.yml")): + self.config_dir = bd + break + if os.getenv("ENCLAVE_PATH") != None: + self.enclave_lib = os.getenv("ENCLAVE_PATH") + else: + for ed in (os.getenv("ENCLAVE_DIR"), ".", "build", "build/target/debug"): + if ed == None: continue + if os.path.exists(os.path.join(ed, "libkbupd_enclave.hardened.debug.so")): + self.enclave_lib = os.path.join(ed, "libkbupd_enclave.hardened.debug.so") + break + elif os.path.exists(os.path.join(ed, "libkbupd_enclave.debug.so")): + self.enclave_lib = os.path.join(ed, "libkbupd_enclave.debug.so") + break + if not hasattr(self, "kbupd_bin"): + raise Exception("Couldn't find kbupd binary, maybe set BIN_DIR?") + if not hasattr(self, "enclave_lib"): + raise Exception("Couldn't find enclave .so, maybe set ENCLAVE_DIR?") + if not hasattr(self, "config_dir"): + raise Exception("Couldn't find replica.client_test.yml, maybe set CONFIG_DIR?") + + append_log = kwargs.get("append_log", False) + + if kwargs.get("debug", True): + debug_args = ["--debug"] + else: + debug_args = [] + + self.control_port = port + + self.log_file = "kbupd-%s.log" % self.control_port + + self.peer_ca_path = ca.cert_path() + self.peer_key_file = tempfile.NamedTemporaryFile(prefix="server-", suffix=".pem") + self.peer_key_file.write(ca.generate_peer_certificate()) + self.peer_key_file.flush() + + if os.getenv("ENCLAVE_DEBUG") != None: + enclave_debug_args = ["--enclave-debug", os.getenv("ENCLAVE_DEBUG")] + else: + enclave_debug_args = [] + + if os.getenv("PROFILE") != None: + perf_args = ["env", "SGX_DBG_OPTIN=1", + "perf", "record", "-F", "9997", "-g", + "-o", "kbupd-%s-perf.data" % self.control_port] + else: + perf_args = [] + + if os.getenv("IAS_TLS_CONFIG") != None: + if os.getenv("IAS_SPID") == None: + raise Exception("Must set IAS_SPID with IAS_TLS_CONFIG") + ias_args = ["--ias-tls-config-file", os.getenv("IAS_TLS_CONFIG"), + "--ias-spid", os.getenv("IAS_SPID")] + else: + ias_args = [] + + if subcommand == "replica": + self.peer_port = port + 1 + config_file = kwargs.get("config_file") + if config_file == None: + config_file = "replica.client_test.yml" + config_file_args = ["--config-dir", self.config_dir, "--config-file", config_file] + peer_port_args = ["--listen-peers", "127.0.0.1:%s" % self.peer_port] + exit_signals_args = ["--exit-signals"] + else: + self.api_port = port + 1 + config_file = kwargs.get("config_file") + if config_file == None: + config_file = "frontend.client_test.yml" + config_file_args = ["--config-dir", self.config_dir, "--config-file", config_file] + peer_port_args = ["--listen-api", "127.0.0.1:%s" % self.api_port] + exit_signals_args = [] + + self.net_cls_group = kwargs.get('net_cls_group', None) + self.net_cls_id = kwargs.get('net_cls_id', None) + if not self.net_cls_group: + self.net_cls_group = 'kbupd-FE' + self.net_cls_id = 1337 + create_cgroup(self.net_cls_group, self.net_cls_id) + kbupd_args = ['cgexec', '-g', 'net_cls:%s' % self.net_cls_group, + self.kbupd_bin, "--enclave-directory", os.path.dirname(self.enclave_lib), + "--listen-control", "127.0.0.1:%s" % self.control_port, + "--kbuptlsd-bin-file", self.kbuptlsd_bin, + *debug_args, + *config_file_args, + *ias_args, + subcommand, + "--enclave", os.path.basename(self.enclave_lib)[:-3], + "--peer-key-file", self.peer_key_file.name, + "--peer-ca-file", self.peer_ca_path, + *enclave_debug_args, + *exit_signals_args, + *peer_port_args, + *args] + print(' '.join(kbupd_args)) + with open(self.log_file, 'a' if append_log else 'w') as logfd: + self.proc = subprocess.Popen(perf_args + kbupd_args, + stdin=subprocess.DEVNULL, + stdout=logfd, + stderr=subprocess.STDOUT, + env={"RUST_BACKTRACE": "1"}, + close_fds=True) + Kbupd.processes.append(self.proc) + if subcommand == "frontend": + Kbupd.frontends.append(self) + + while not hasattr(self, "node_id"): + self.refresh_info() + time.sleep(0.1) + + def __str__(self): + return str(self.node_id) + + def kill(self): + metrics = self.kbupctl('metrics').stdout.decode() + self.proc.terminate() + self.proc.wait() + with open(self.log_file, 'a') as logfd: + logfd.write(metrics) + logfd.write('\n') + try: + Kbupd.processes.remove(self.proc) + except ValueError: + pass + try: + Kbupd.frontends.remove(self) + except ValueError: + pass + + def sigstop(self): + self.proc.send_signal(signal.SIGSTOP) + + def sigcont(self): + self.proc.send_signal(signal.SIGCONT) + + @classmethod + def kill_all(cls): + for proc in cls.processes: + print(DIM + "Killing process %s" % proc.pid + CLEAR) + proc.kill() + + def refresh_info(self): + info = self.kbupctl("info") + info_err = info.stderr.decode() + info_out = info.stdout.decode() + if info_err.find("Connection refused") != -1 or info_err.find("Connection reset") != -1: + time.sleep(0.1) + if self.proc.poll() != None: + raise Exception("kbupd-%s terminated: %s" % (self.control_port, self.proc.returncode)) + return + for key_val in [l for l in info_out.split('\n') if len(l) > 0]: + key, val = key_val.split('=') + if not hasattr(self, key.lower()): + print(DIM + key_val + CLEAR) + setattr(self, key.lower(), val) + + def kbupctl(self, *args): + return subprocess.run([self.kbupctl_bin, + "--connect", "127.0.0.1:%s" % self.control_port, *args], + stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, close_fds=True) + + def kbupctl_async(self, *args): + return subprocess.Popen([self.kbupctl_bin, + "--connect", "127.0.0.1:%s" % self.control_port, *args], + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, close_fds=True) + + def grep_log(self, regex): + with open(self.log_file, 'r') as logfd: + return [ line[:-1] for line in logfd if re.search(regex, line) ] + + def reconnect_peer(self, peer_node_id, address=None): + address_args = [] + if address != None: + address_args = ["--peer-address", address] + self.kbupctl("reconnect-peer", "--peer-node-id", peer_node_id, *address_args) + + def disconnect_peer(self, peer_node_id): + self.kbupctl("disconnect-peer", "--peer-node-id", peer_node_id) + + def get_backup_count(self): + level = 0 + is_leader = False + for line in self.kbupctl("status").stdout.decode().split('\n'): + if line.strip().startswith("partition: EnclaveReplicaPartitionStatus"): + level = 1 + elif level >= 1 and line.endswith('{'): + level += 1 + elif level >= 1 and line.endswith('},'): + level -= 1 + elif level == 1 and "is_leader:" in line: + is_leader = 'true' in line.strip().split(':')[1] + elif level == 1 and "backup_count:" in line: + backup_count = int(line.strip().split(':')[1][1:-1]) + + if is_leader: + return backup_count + else: + return None diff --git a/service/test/kbupdclient.py b/service/test/kbupdclient.py new file mode 100644 index 0000000..e703261 --- /dev/null +++ b/service/test/kbupdclient.py @@ -0,0 +1,86 @@ +import atexit, re, time +from datetime import datetime + +from util import eprint + +NUM_RETRIES=90 + +def timestamp(): + return datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + +class KbupdClient(): + log = None + + def __init__(self, frontend, enclave_name, service_id): + self.frontend = frontend + self.enclave_name = enclave_name + self.service_id = service_id + if not self.log: + KbupdClient.log = open("client_test.log", 'w') + atexit.register(KbupdClient.log.close) + + def request(self, regex, subcommand, backup_id = None, pin = None, + data = None, tries = None, service_id = None, token = None, valid_from = None, + count = None): + if subcommand == "backup" or subcommand == "restore": + if service_id == None: + service_id = self.service_id + + for retry in range(0, NUM_RETRIES): + cmd = ["client", "--enclave-name", self.enclave_name, subcommand] + if backup_id != None: + cmd.extend(["--backup-id", backup_id]) + if pin != None: + cmd.extend(["--backup-pin", str(pin)]) + if data != None: + cmd.extend(["--backup-data", data]) + if tries != None: + cmd.extend(["--backup-tries", str(tries)]) + if service_id != None: + cmd.extend(["--service-id", str(service_id)]) + if token != None: + cmd.extend(["--request-token", token]) + if valid_from != None: + cmd.extend(["--request-valid-from", str(valid_from)]) + if count != None: + cmd.extend(["--request-count", str(count)]) + + if retry == 0: + self.log.write(timestamp() + " CMD: " + ' '.join(cmd) + '\n') + + kbupctl = self.frontend.kbupctl(*cmd) + output = kbupctl.stdout.decode() + stderr = kbupctl.stderr.decode() + + #Some errors are normal and expected, the client must retry. XferInProgress + # causes cancelled requests. And TokenMismatches can happen as a result of + # a leader election. + if re.search(regex, output) or re.search(regex, stderr): #Success + break + elif (re.search(r'request canceled by enclave', stderr) and \ + re.search(r'ControlErrorSignal', stderr)) or \ + re.search(r'status=TokenMismatch', output): #Retry + match = re.search(r"token=([0-9a-fA-F]*)", output) + if match != None: + token = match.groups()[0] + time.sleep(2) + else: #Fail + self.log.write("CMD FAILED\n") + break + + self.log.write(timestamp() + " COMPLETED" + + ("" if retry == 0 else " retried: %s" % (retry,)) + "\n") + self.log.write(stderr) + self.log.flush() + + if re.search(regex, output) == None and re.search(regex, stderr) == None: + eprint() + eprint("TEST '%s' FAILED, expecting %s, got:" % (" ".join(cmd), regex)) + eprint(output) + raise Exception("Test failed") + + res_token = "" + match = re.search(r"token=([0-9a-fA-F]*)", output) + if match != None: + res_token = match.groups()[0] + return res_token diff --git a/service/test/ne_latency.yml b/service/test/ne_latency.yml new file mode 100644 index 0000000..3ac1441 --- /dev/null +++ b/service/test/ne_latency.yml @@ -0,0 +1,7 @@ +config: + loop: true +steps: + - netem: &base + latency: + ms: 120 + - sleep_ms: 3000000 diff --git a/service/test/ne_latency_loss.yml b/service/test/ne_latency_loss.yml new file mode 100644 index 0000000..2ecb6bc --- /dev/null +++ b/service/test/ne_latency_loss.yml @@ -0,0 +1,14 @@ +config: + loop: true +steps: + - netem: &base + latency: + ms: 120 + - sleep_ms: 30000 + - netem: + loss: + pct: 50 + correlation_pct: 50 + - sleep_ms: 5000 + - netem: *base + - sleep_ms: 30000 diff --git a/service/test/ne_latency_variance.yml b/service/test/ne_latency_variance.yml new file mode 100644 index 0000000..5670b61 --- /dev/null +++ b/service/test/ne_latency_variance.yml @@ -0,0 +1,10 @@ +config: + loop: true +steps: + - netem: &base + latency: + ms: 120 + variation_ms: 20 + variation_correlation_pct: 25 + variation_distribution: paretonormal + - sleep_ms: 60000 diff --git a/service/test/netem.py b/service/test/netem.py new file mode 100644 index 0000000..9d69e6a --- /dev/null +++ b/service/test/netem.py @@ -0,0 +1,358 @@ +import subprocess, atexit, threading, yaml, os, re, socket +from collections.abc import Mapping +from partition import Partition +from kbupd import Kbupd +from util import get_ppid, choices + +DEV='lo' + +first_netem = True + +checked_ss = False +ss = './ss' + +stopped = [] + +iptables = set() + +first_rewrite = True + +def netem_del(): + global first_netem + subprocess.run(['sudo', 'tc', 'qdisc', 'del', 'dev', DEV, 'root'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + first_netem = True + +def iptables_add(rule): + global iptables + if len(iptables) == 0: + atexit.register(iptables_flush) + iptables.add(tuple(rule)) + +def iptables_commit(): + global iptables + + old_rules = subprocess.run(['sudo', 'iptables-save', '-c'], check=True, + stdout=subprocess.PIPE, universal_newlines=True).stdout.split('\n') + + new_rules = [] + table = None + for rule in old_rules: + if len(rule) > 0: + if rule[0] == '*': + table = rule[1:] + elif table == 'filter' and 'OUTPUT' in rule.split(): + continue + elif rule == 'COMMIT' and table == 'filter': + new_rules.extend([' '.join(r) for r in iptables]) + new_rules.append(rule) + + subprocess.run(['sudo', 'iptables-restore', '-T', 'filter'], + input='\n'.join(new_rules), universal_newlines=True, check=True) + +def iptables_flush(): + global iptables + + subprocess.run(['sudo', 'iptables', '-F', 'OUTPUT'], check=True) + iptables = set() + atexit.unregister(iptables_flush) + +def iptables_nat_add(rule): + global first_rewrite + if first_rewrite: + atexit.register(iptables_nat_flush) + first_rewrite = False + subprocess.run(rule, check=True) + +def iptables_nat_commit(): + pass + +def iptables_nat_flush(): + subprocess.run(['sudo', 'iptables', '-t', 'nat', '-F', 'OUTPUT'], check=True) + +class PlaybackThread(threading.Thread): + #XXX Add ability to apply loss or latency to specific nodes and/or nodepairs. + def verb_netem(self, latency = None, loss = None): + global first_netem + if first_netem: + netem_del() + verb = 'add' + atexit.register(netem_del) + first_netem = False + else: + verb = 'change' + assert(latency or loss) + + args = [] + + if latency: + latency_cpy = dict(latency) + if 'ms' in latency: + args.extend(['delay', str(latency['ms']) + 'ms']) + del(latency_cpy['ms']) + if 'variation_ms' in latency: + assert('ms' in latency) + args.extend([str(latency['variation_ms']) + 'ms']) + del(latency_cpy['variation_ms']) + if 'variation_correlation_pct' in latency: + assert('variation_ms' in latency) + args.extend([str(latency['variation_correlation_pct']) + '%']) + del(latency_cpy['variation_correlation_pct']) + if 'variation_distribution' in latency: + assert('variation_ms' in latency) + args.extend(['distribution', str(latency['variation_distribution'])]) + del(latency_cpy['variation_distribution']) + assert(latency_cpy == {}) + + if loss: + loss_cpy = dict(loss) + if 'pct' in loss: + args.extend(['loss', str(loss['pct']) + '%']) + del(loss_cpy['pct']) + if 'correlation_pct' in loss: + assert('pct' in loss) + args.extend([str(loss['correlation_pct']) + '%']) + del(loss_cpy['correlation_pct']) + assert(loss_cpy == {}) + + print('NETEM:', + ' '.join(['sudo', 'tc', 'qdisc', verb, 'dev', DEV, 'root', 'netem', *args]), + flush=True) + subprocess.run(['sudo', 'tc', 'qdisc', verb, 'dev', DEV, 'root', 'netem', *args], + check=True) + + def verb_randstop(self, replicas_per_partition = 1, num_partitions = 0): + global stopped + if len(Partition.partitions) == 0: + return + if num_partitions == 0: + partitions = Partition.partitions + else: + partitions = choices(Partition.partitions, k=num_partitions) + for partition in partitions: + if len(partition.peers) == 0: + continue + for replica in choices(partition.peers, k=replicas_per_partition): + print('RANDSTOP:', replica, flush=True) + replica.sigstop() + stopped.append(replica) + + def verb_contall(self): + global stopped + for replica in set(stopped): + print('CONTALL:', replica, flush=True) + replica.sigcont() + stopped = [] + + def verb_randtcpkill(self, num_replicas = 1): + global checked_ss, ss + if not checked_ss: + if not os.access(ss, os.X_OK): + ss = 'ss' + out = subprocess.run([ss, '-h'], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT).stdout.decode() + if not '-K,' in out.split(): + #Try putting a newer 'ss' binary in the same dir as the tests. + raise Exception("randtcpkill requires 'ss' to support -K") + checked_ss = True + + replicas = [] + if len(Partition.partitions) == 0: + return + for partition in Partition.partitions: + if len(partition.peers) == 0: + continue + for replica in partition.peers: + replicas.append(replica) + if num_replicas == 0: + num_replicas = len(replicas) + for replica in choices(replicas, k=num_replicas): + cmd = ['sudo', ss, '-K', 'dst', '127.0.0.1', + 'dport', '=', str(replica.peer_port)] + print('RANDTCPKILL:', ' '.join(cmd), flush=True) + subprocess.run(cmd) + + def verb_adddrop(self, src, dst, single_conn=False): + for partition in Partition.partitions: + leader, followers = partition.get_replicas() + + rule = ['-A', 'OUTPUT'] + + if dst == 'any': + dstport = None + elif dst == 'FE': + assert(False) #replicas don't connect back to FEs. + elif dst == 'leader': + if not leader: + continue + dstport = leader.peer_port + elif dst.startswith('replica'): + num = int(dst[7:]) + if num >= len(followers): + continue + dstport = followers[num].peer_port + else: + assert(False) + if dstport: + rule.extend(['-p', 'tcp', '-m', 'tcp', '--dport', '%s' % dstport]) + + if single_conn: + assert(dstport) + conns = subprocess.run(['ss', '-n', '-p', + 'state', 'established', '( dport = :%s )' % dstport], + stdout=subprocess.PIPE, universal_newlines=True, check=True).stdout + for conn in conns.split('\n')[1:]: + m = re.search(r'tcp +[0-9]+ +[0-9]+ +[0-9.]+:([0-9]+).*pid=([0-9]+)', conn) + if m: + srcport = int(m[1]) + kbuptlsd_pid = int(m[2]) + kbupd_pid = get_ppid(kbuptlsd_pid) + + if not os.readlink('/proc/%s/exe' % kbuptlsd_pid).endswith('/kbuptlsd'): + continue + + if src == 'FE': + assert(len(Kbupd.frontends) <= 1) #XXX We only support 1 FE + if len(Kbupd.frontends) > 0 and Kbupd.frontends[0].proc.pid == kbupd_pid: + break + elif src == 'leader': + if not leader: + continue + if leader.proc.pid == kbupd_pid: + break + elif src.startswith('replica'): + num = int(src[7:]) + if num >= len(followers): + continue + if followers[num].proc.pid == kbupd_pid: + break + else: + assert(False) + else: + #Didn't find any connections. + continue + rule.extend(['--sport', '%s' % srcport]) + else: + if src == 'any': + classid = None + elif src == 'FE': + classid = 1337 + elif src == 'leader': + if not leader: + continue + classid = leader.net_cls_id + elif src.startswith('replica'): + num = int(src[7:]) + if num >= len(followers): + continue + classid = followers[num].net_cls_id + else: + assert(False) + if classid: + rule.extend(['-m', 'cgroup', '--cgroup', '%s' % classid]) + + rule.extend(['-j', 'DROP']) + print('ADDDROP:', rule, flush=True) + iptables_add(rule) + iptables_commit() + + def verb_droptohost(self, host, send_rst=False): + try: + iplist = socket.gethostbyname_ex(host)[2] + except socket.gaierror: + time.sleep(5) + iplist = socket.gethostbyname_ex(host)[2] + for ip in iplist: + rule = ['-A', 'OUTPUT', '-p', 'tcp', '-d', ip ] + if send_rst: + #XXX Don't fail attestation for FEs, because that causes them to crash. + rule.extend(['-m', 'cgroup', '!', '--cgroup', '1337']) + rule.extend(['-j', 'REJECT']) + else: + rule.extend(['-j', 'DROP']) + print('DROPTOHOST:', rule, flush=True) + iptables_add(rule) + iptables_commit() + + def verb_flushdrop(self): + print('FLUSHDROP', flush=True) + iptables_flush() + + def verb_rewritedst(self, old_dst, new_dst): + try: + iplist = socket.gethostbyname_ex(old_dst)[2] + except socket.gaierror: + time.sleep(5) + iplist = socket.gethostbyname_ex(old_dst)[2] + try: + new_dst_ip = socket.gethostbyname(new_dst) + except socket.gaierror: + time.sleep(5) + new_dst_ip = socket.gethostbyname(new_dst) + for ip in iplist: + rule = ['sudo', 'iptables', '-t', 'nat', '-A', 'OUTPUT', + '-m', 'cgroup', '!', '--cgroup', '1337', + '-p', 'tcp', '-d', ip, '-j', 'DNAT', '--to-destination', new_dst_ip] + print('REWRITEDST', rule, flush=True) + iptables_nat_add(rule) + iptables_nat_commit() + + def verb_flushrewrite(self): + print('FLUSHREWRITE', flush=True) + iptables_nat_flush() + + def verb_sleep_ms(self, ms): + print('SLEEP_MS', ms, flush=True) + self.exit.wait(ms / 1000.0) + + def verb_println(self, text): + print('PRINTLN', text, flush=True) + + def run(self): + def _run_cleanup(): + self.verb_flushrewrite() + self.verb_flushdrop() + netem_del() + self.verb_contall() + + config = self.script_yaml.get('config', {}) + loop = config.get('loop', False) + while not self.exit.is_set(): + for step in self.script_yaml['steps']: + if self.exit.is_set(): + break + + assert(len(step.keys()) == 1) + verb, args = list(step.items())[0] + boundfn = getattr(self, 'verb_' + verb, None) + assert(boundfn) + if args is None: + boundfn() + elif isinstance(args, Mapping): + boundfn(**args) + else: + boundfn(args) #arg, really. + + if not loop: + _run_cleanup() + return + _run_cleanup() + +def start_playback(*filenames): + threads = [] + for filename in filenames: + with open(filename, 'r') as f: + script_yaml = yaml.safe_load(f) + thread = PlaybackThread(name='netem_playback_' + filename, daemon=True) + thread.script_yaml = script_yaml + thread.exit = threading.Event() + print('Playback thread for %s starting.' % filename, flush=True) + thread.start() + threads.append(thread) + return threads + +if __name__ == "__main__": + import sys, time + threads = start_playback(*sys.argv[1:]) + while [t for t in threads if t.is_alive()]: + time.sleep(1) diff --git a/service/test/partition.py b/service/test/partition.py new file mode 100644 index 0000000..c7029bc --- /dev/null +++ b/service/test/partition.py @@ -0,0 +1,194 @@ +import time, re + +from util import gprint, backup_id_to_str, str_to_backup_id, create_cgroup +from kbupd import Kbupd + +NUM_RETRIES=90 + +class Partition(): + port = 31337 + partitions = [] + + def __init__(self, + ca, + first_id = backup_id_to_str(0), + last_id = backup_id_to_str(2**256-1), + source_partition = None, + replicas = 3, + config_file = None, + debug = True, + storage_size = None): + self.ca = ca + self.first_id = first_id + self.last_id = last_id + self.config_file = config_file + self.debug = debug + self.peers = [] + + source_nodes_cmd = [] + if source_partition: + source_nodes_cmd.append("--firstid") + source_nodes_cmd.append(first_id) + source_nodes_cmd.append("--lastid") + source_nodes_cmd.append(last_id) + source_nodes_cmd.append("--source-nodes") + source_nodes_cmd.append(source_partition.peer_addrs) + + storage_size_cmd = [] + if storage_size: + storage_size_cmd.append("--storage-size") + storage_size_cmd.append(str(storage_size)) + + replica_ip_ports = ','.join([ "127.0.0.1:%s" % (Partition.port + 1 + replica_num * 2) for replica_num in range(replicas)]) + + for num in range(replicas): + net_cls_group = 'kbupd-%s' % num + net_cls_id = (num * 2) + 31337 + create_cgroup(net_cls_group, net_cls_id) + replica = Kbupd(Partition.port, "replica", self.ca, + "--replicas", replica_ip_ports, + config_file = config_file, + debug = debug, + net_cls_group = net_cls_group, + net_cls_id = net_cls_id, + *source_nodes_cmd, + *storage_size_cmd) + gprint("Started replica %s: %s (%s)" % (num, replica.node_id, replica.enclave_lib)) + self.peers.append(replica) + Partition.port += 2 + + self.peers = sorted(self.peers, key=lambda peer: peer.node_id, reverse=True) + self.peer_addrs = ','.join([ "127.0.0.1:%s" % (p.peer_port) for p in self.peers]) + + #Destination replicas don't have a service ID yet. + for peer in self.peers: + while True: + peer.refresh_info() + if hasattr(peer, "group_id"): + if hasattr(self, "group_id"): + assert(self.group_id == peer.group_id) + else: + self.group_id = peer.group_id + self.service_id = getattr(peer, "service_id", "") + break + time.sleep(0.1) + + while len(self.grep_logs(r"raft.*=== became leader at")) <= 0: + time.sleep(0.1) + + Partition.partitions.append(self) + + def get_spec(self): + return "%s-%s=%s" % (self.first_id, self.last_id, self.peer_addrs) + + def get_spec_no_range(self): + return "=%s" % (self.peer_addrs) + + def log_replica_statuses(self, header): + if not hasattr(self, 'status_log'): + self.status_log = open('stat-' + self.group_id[0:10] + '.log', 'w') + self.status_log.write(header + ' at ' + time.asctime() + '\n') + for peer in self.peers: + status = peer.kbupctl('status') + self.status_log.write(status.stdout.decode()) + self.status_log.flush() + + def split_partition(self): + last_id = str_to_backup_id(self.last_id) + first_id = str_to_backup_id(self.first_id) + split = first_id + (last_id - first_id)//2 + new_partition = Partition(self.ca, self.first_id, backup_id_to_str(split), source_partition = self, + replicas = len(self.peers), + config_file = self.config_file, + debug = self.debug) + self.first_id = backup_id_to_str(split + 1) + + return new_partition + + def move_partition(self): + return Partition(self.ca, self.first_id, self.last_id, source_partition = self, + replicas = len(self.peers), + config_file = self.config_file, + debug = self.debug) + + def partition_command(self, command): + for retry in range(0, NUM_RETRIES): + ok_peers = 0 + for peer in self.peers: + proc = peer.kbupctl("xfer", command) + if proc.stdout.decode().startswith('ok'): + ok_peers += 1 + if ok_peers > 0: + return + time.sleep(2) + raise Exception("partition_command timed out!") + + def start_partition(self): + self.log_replica_statuses('start_partition') + self.partition_command("start") + + def resume_partition(self): + self.partition_command("resume") + + def pause_partition(self): + self.partition_command("pause") + + def wait_partition_started_source(self): + while len(self.grep_logs(r"=== starting xfer")) < 1: + time.sleep(0.1) + #We wait for only 1 replica, not all, so there is an improbable race + # where a replica will get a resume before a "starting xfer" and log + # an error. Just here to reduce log noise: + time.sleep(0.2) + + #Wait for the partitioning to be done. + def wait_partition_source(self): + #XXX Doesn't handle more than 1 partitioning! Count start/completed? + while len(self.grep_logs(r"=== All transfers sent")) < 1: + time.sleep(0.1) + self.log_replica_statuses('wait_partition_source') + def wait_partition_destination(self): + while len(self.grep_logs(r"=== All transfer chunks applied")) < 1: + time.sleep(0.1) + self.log_replica_statuses('wait_partition_destination') + + def finish_partition(self): + self.partition_command("finish") + + def grep_logs(self, regex): + results = [] + for peer in self.peers: + results.extend(peer.grep_log(regex)) + return results + + def get_replicas(self): + #XXX Sould make the leader the peer with the most other peers (including itself) + # who think it is the leader, as long as that is more than quorum. + leader, max_term = None, -1 + for peer in self.peers: + status = peer.kbupctl("status").stdout.decode() + m = re.search(r'is_leader: ([a-z]+),\n[ ]+current_term: ([0-9]+),', status) + if m: + is_leader, term = m.groups() + if is_leader == 'true' and int(term) > max_term: + leader = peer + max_term = int(term) + followers = set(self.peers) + followers.discard(leader) + return (leader, list(followers)) + + def kill(self, peer=None): + if peer: + peer.kill() + self.peers.remove(peer) + else: + while len(self.peers) > 0: + self.peers.pop().kill() + self.peer_addrs = ','.join([ "127.0.0.1:%s" % (p.peer_port) for p in self.peers]) + + def get_backup_count(self): + for peer in self.peers: + count = peer.get_backup_count() + if count is not None: + return count + return None diff --git a/service/test/peer_ca.py b/service/test/peer_ca.py new file mode 100644 index 0000000..9f582c4 --- /dev/null +++ b/service/test/peer_ca.py @@ -0,0 +1,38 @@ +import subprocess, tempfile + +class PeerCa(): + def __init__(self): + self.ca_key_file = tempfile.NamedTemporaryFile(prefix="ca-", suffix=".key") + self.ca_cert_file = tempfile.NamedTemporaryFile(prefix="ca-", suffix=".pem") + self.prev_serial = 0 + self.openssl("ecparam", "-name", "secp384r1", "-genkey", "-out", self.ca_key_file.name) + self.openssl("req", "-new", "-x509", "-batch", "-subj", "/CN=kbupd_client_test_ca/", + "-key", self.ca_key_file.name, + "-out", self.ca_cert_file.name) + + def cert_path(self): + return self.ca_cert_file.name + + def generate_peer_certificate(self): + self.prev_serial += 1 + key = self.openssl("ecparam", "-name", "secp384r1", "-genkey") + req = self.openssl("req", "-new", "-batch", "-key", "-", "-subj", "/CN=kbupd_client_test_peer_%s/" % (self.prev_serial), stdin=key) + cert = self.openssl("x509", "-req", + "-CA", self.ca_cert_file.name, + "-CAkey", self.ca_key_file.name, + "-set_serial", str(self.prev_serial), + stdin=req) + return self.openssl("pkcs12", "-export", "-chain", "-passout", "pass:", + "-CAfile", self.ca_cert_file.name, + stdin=key + cert) + + def openssl(self, *args, **kwargs): + openssl = subprocess.Popen(["openssl", *args], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + stdout, stderr = openssl.communicate(kwargs.get("stdin")) + if openssl.returncode != 0: + raise Exception("openssl %s terminated: %s\n%s" % (args, openssl.returncode, stderr)) + return stdout diff --git a/service/test/randtcpkill.yml b/service/test/randtcpkill.yml new file mode 100644 index 0000000..07afb0c --- /dev/null +++ b/service/test/randtcpkill.yml @@ -0,0 +1,6 @@ +config: + loop: true +steps: + - sleep_ms: 5000 + - randtcpkill: + num_replicas: 1 diff --git a/service/test/randtcpkill_2x.yml b/service/test/randtcpkill_2x.yml new file mode 100644 index 0000000..e2ef5d7 --- /dev/null +++ b/service/test/randtcpkill_2x.yml @@ -0,0 +1,9 @@ +config: + loop: true +steps: + - sleep_ms: 5000 + - randtcpkill: + num_replicas: 1 + - sleep_ms: 10 + - randtcpkill: + num_replicas: 2 diff --git a/service/test/splitbrain.yml b/service/test/splitbrain.yml new file mode 100644 index 0000000..36c5341 --- /dev/null +++ b/service/test/splitbrain.yml @@ -0,0 +1,11 @@ +config: + loop: true +steps: + - sleep_ms: 60000 + - flushdrop: + - adddrop: + src: 'leader' + dst: 'replica1' + - adddrop: + src: 'replica1' + dst: 'leader' diff --git a/service/test/start_stop.yml b/service/test/start_stop.yml new file mode 100644 index 0000000..3d2f534 --- /dev/null +++ b/service/test/start_stop.yml @@ -0,0 +1,7 @@ +config: + loop: true +steps: + - sleep_ms: 30000 + - randstop: + - sleep_ms: 70000 + - contall: diff --git a/service/test/start_stop_2x.yml b/service/test/start_stop_2x.yml new file mode 100644 index 0000000..3d923b4 --- /dev/null +++ b/service/test/start_stop_2x.yml @@ -0,0 +1,9 @@ +config: + loop: true +steps: + - sleep_ms: 30000 + - randstop: + - sleep_ms: 50000 + - randstop: + - sleep_ms: 20000 + - contall: diff --git a/service/test/test_loop b/service/test/test_loop new file mode 100755 index 0000000..22ae628 --- /dev/null +++ b/service/test/test_loop @@ -0,0 +1,81 @@ +#!/bin/bash + +TIMEOUT=${TIMEOUT:=5400} + +function dead_procs { + for pid in `pidof $1`; do + if cut -d' ' -f3 < /proc/$pid/stat | grep -vq Z; then + echo "Stale $1 processes are running!" >&2 + exit 99 + fi + done +} +dead_procs kbupd +dead_procs kbuptlsd + +if sudo iptables -L -n | grep -q ^DROP; then + echo "Stale iptables OUTPUT rule" >&2 + exit 98 +fi + +if [ -z "$IAS_SPID" ]; then + echo "run: . ./env" >&2 + exit 97 +fi + +our_pid=$$ +function timeout { + echo Test run took longer than $TIMEOUT seconds! >&2 + kill -s TERM %2 +} +trap timeout ALRM + +function finish { + kill %2 >/dev/null 2>&1 + kill %1 >/dev/null 2>&1 +} +trap finish EXIT + +SKIP_ERRO="bad client reply:" +#XXX Some (all?) of the lines below here should be fixed by reducing the log level in the enclave. +SKIP_ERRO="${SKIP_ERRO}|request canceled by enclave" +SKIP_ERRO="${SKIP_ERRO}|with requested range outside our range" +SKIP_ERRO="${SKIP_ERRO}|resume partitioning as a non-source replica" +SKIP_ERRO="${SKIP_ERRO}|error fetching revocation list from IAS" +SKIP_ERRO="${SKIP_ERRO}|error validating attestation report" +SKIP_ERRO="${SKIP_ERRO}|is now invalid at" #XXX Unclear if this is actually harmless. +SKIP_ERRO="${SKIP_ERRO}|slog-async: logger dropped messages due to channel overflow" #XXX Until fixed. + +client_out=-1 +i=0 +while true; do + rm -rf previous2 + mv previous previous2 2>/dev/null + mkdir -p previous + rm -f previous/* + mv *.log previous 2>/dev/null + date + ( sleep $TIMEOUT; kill -s ALRM $our_pid ) & + ./client_test.py $* >out.log 2>&1 /dev/null 2>&1 + wait %1 >/dev/null 2>&1 + + i=$((i+1)) + echo $i `wc -l *.log | awk '{print $1,$2}' | tr '\n' ' ' | sed 's/[.]log//g' | sed 's/up//g' | tr -d '-'` + + if egrep -q '(ERROR|FAILED|Traceback)' out.log; then + echo "Error in out.log!" >&2 + break + fi + if ! tail -n1 out.log | grep -q '^OK$'; then + echo "Tests failed" >&2 + break + fi + if [ `grep ERRO *.log | egrep -v "$SKIP_ERRO" | wc -l` -gt 0 ]; then + echo "Unexpected ERRO in logs" >&2 + break + fi +done + +date diff --git a/service/test/util.py b/service/test/util.py new file mode 100644 index 0000000..2d5cbd9 --- /dev/null +++ b/service/test/util.py @@ -0,0 +1,48 @@ +import random, sys, subprocess + +cgroups = set() + +DIM = '\033[2m' +RED = '\033[31m' +GREEN = '\033[32m' +CLEAR = '\033[0m' + +def eprint(*args, **kwargs): + print(RED, end='', file=sys.stderr) + print(*args, file=sys.stderr, **kwargs) + print(CLEAR, end='', file=sys.stderr, flush=True) +def gprint(*args, **kwargs): + print(GREEN, end='') + print(*args, **kwargs) + print(CLEAR, end='', flush=True) + +def backup_id_to_str(bid): + return hex(bid)[2:].rjust(64, '0') +def str_to_backup_id(str): + return int(str, base=16) +def random_id(num_bytes): + return hex(random.randint(0, 2**512-1)).rjust(num_bytes*2, '0')[2:2+num_bytes*2] + +def create_cgroup(group, classid): + global cgroups + if group in cgroups: + return + + subprocess.run(['sudo', 'mkdir', '-p', '/sys/fs/cgroup/net_cls/%s' % group], check=True) + subprocess.run('echo %s | sudo tee /sys/fs/cgroup/net_cls/%s/net_cls.classid' % (classid, group), + shell=True, check=True, stdout=subprocess.DEVNULL) + subprocess.run(['sudo', 'cgcreate', '-t', 'signal:signal', '-a', 'signal:signal', + '-g', 'net_cls:%s' % group], check=True) + cgroups.add(group) + +def choices(seq, k): + ret = set() + while len(ret) < min(len(seq), k): + ret.add(random.choice(seq)) + return ret + +def get_ppid(pid): + with open('/proc/%s/stat' % pid, 'r') as stat: + line = stat.readline() + return int(line.split()[3]) +