From: Daniel Kahn Gillmor Date: Sun, 22 Jun 2008 04:27:16 +0000 (-0400) Subject: Merge commit 'mjgoins/master' X-Git-Tag: monkeysphere_0.2-1~11 X-Git-Url: https://codewiz.org/gitweb?p=monkeysphere.git;a=commitdiff_plain;h=3cea2ab969f54fc33ed238c5b326fb3868392a15;hp=b75cb97e42dd3327942d8b32cac2d9ee97e9aa4a Merge commit 'mjgoins/master' --- diff --git a/.gitignore b/.gitignore index 80bf65d..0dc4f79 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,2 @@ *~ *.[ao] -monkeysphere -gpg2ssh -ssh2gpg diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..fefe9ab --- /dev/null +++ b/COPYING @@ -0,0 +1,697 @@ +MonkeySphere is a system to use the OpenPGP web-of-trust to +authenticate and encrypt ssh connections. + +It is free software, developed by: + Jameson Rollins + Daniel Kahn Gillmor + Jamie McClelland + Micah Anderson + Matthew Goins + Mike Castleman + Elliot Winard + Ross Glover + Greg Lyle + +MonkeySphere is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +MonkeySphere Copyright 2007, and are all released under the GPL, +version 3 or later. + + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1a214fc --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +MONKEYSPHERE_VERSION=`head -n1 debian/changelog | sed 's/.*(\([^-]*\)-.*/\1/'` + +all: keytrans + +keytrans: + $(MAKE) -C src/keytrans + +release: clean + rm -rf monkeysphere-$(MONKEYSPHERE_VERSION) + mkdir -p monkeysphere-$(MONKEYSPHERE_VERSION)/doc + ln -s ../../doc/README ../../doc/TODO ../../doc/MonkeySpec monkeysphere-$(MONKEYSPHERE_VERSION)/doc + ln -s ../COPYING ../etc ../Makefile ../man ../src monkeysphere-$(MONKEYSPHERE_VERSION) + tar -ch monkeysphere-$(MONKEYSPHERE_VERSION) | gzip -n > monkeysphere_$(MONKEYSPHERE_VERSION).orig.tar.gz + rm -rf monkeysphere-$(MONKEYSPHERE_VERSION) + +debian-package: release + tar xzf monkeysphere_$(MONKEYSPHERE_VERSION).orig.tar.gz + cp -a debian monkeysphere-$(MONKEYSPHERE_VERSION) + (cd monkeysphere-$(MONKEYSPHERE_VERSION) && debuild -uc -us) + rm -rf monkeysphere-$(MONKEYSPHERE_VERSION) + +clean: + $(MAKE) -C src/keytrans clean + # clean up old monkeysphere packages lying around as well. + rm -f monkeysphere_* + +.PHONY: all clean release debian-package diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..cec0988 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,24 @@ +monkeysphere (0.2-1) UNRELEASED; urgency=low + + [ Daniel Kahn Gillmor ] + * NOT YET RELEASED (switch to "experimental" when ready to release) + + [ Jameson Graef Rollins ] + * Add AUTHORIZED_USER_IDS config variable for server, which defaults to + %h/.config/monkeysphere/authorized_user_ids, instead of + /etc/monkeysphere/authorized_user_ids. + * Remove {update,remove}-userids functions, since we decided they + weren't useful enough to be worth maintaining. + * Better handling of unknown users in server update-users + * Add file locking when modifying known_hosts or authorized_keys + * Better failure/prompting for gen-subkey + + -- Jameson Graef Rollins Sat, 21 Jun 2008 16:39:26 -0400 + +monkeysphere (0.1-1) experimental; urgency=low + + * First release of debian package for monkeysphere. + * This is experimental -- please report bugs! + + -- Daniel Kahn Gillmor Thu, 19 Jun 2008 00:34:53 -0400 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..d4d25c6 --- /dev/null +++ b/debian/control @@ -0,0 +1,22 @@ +Source: monkeysphere +Section: net +Priority: extra +Maintainer: Daniel Kahn Gillmor +Uploaders: Jameson Rollins +Build-Depends: debhelper (>= 7.0), libgnutls-dev (>= 2.3.14) +Standards-Version: 3.8.0.1 +Homepage: http://cmrg.fifthhorseman.net/wiki/OpenPGPandSSH +Dm-Upload-Allowed: yes + +Package: monkeysphere +Architecture: any +Depends: openssh-client, gnupg | gnupg2, coreutils (>= 6), moreutils, ${shlibs:Depends} +Recommends: netcat +Enhances: openssh-client, openssh-server +Description: use the OpenPGP web of trust to verify ssh connections + SSH key-based authentication is tried-and-true, but it lacks a true + Public Key Infrastructure for key certification, revocation and + expiration. MonkeySphere is a framework that uses the OpenPGP web of + trust for these PKI functions. It can be used in both directions: + for users to get validated host keys, and for hosts to manage user + permissions. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..1fa6d83 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,24 @@ +Format-Specification: http://wiki.debian.org/Proposals/CopyrightFormat +Debianized-By: Daniel Kahn Gillmor +Debianized-Date: Fri Jun 13 10:19:16 EDT 2008 +Original-Source: http://lair.fifthhorseman.net/~dkg/git/monkeysphere.git/ + +Files: * +Copyright: 2008 Jameson Rollins , + Daniel Kahn Gillmor , + Jamie McClelland , + Micah Anderson , + Matthew Goins , + Mike Castleman , + Elliot Winard , + Ross Glover , + Greg Lyle + +License: GPL-3+ + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + . + On Debian systems, the complete text of the GNU General Public License + can be found in file "/usr/share/common-licenses/GPL". diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 0000000..b458649 --- /dev/null +++ b/debian/dirs @@ -0,0 +1,11 @@ +var/cache/monkeysphere +var/cache/monkeysphere/authorized_keys +usr/bin +usr/sbin +usr/share +usr/share/monkeysphere +usr/share/man +usr/share/man1 +usr/share/man8 +etc/monkeysphere +etc/monkeysphere/authorized_user_ids diff --git a/debian/monkeysphere.dirs b/debian/monkeysphere.dirs new file mode 100644 index 0000000..6e90899 --- /dev/null +++ b/debian/monkeysphere.dirs @@ -0,0 +1,4 @@ +usr/share/monkeysphere +var/cache/monkeysphere +var/cache/monkeysphere/authorized_keys +etc/monkeysphere diff --git a/debian/monkeysphere.docs b/debian/monkeysphere.docs new file mode 100644 index 0000000..4b8144e --- /dev/null +++ b/debian/monkeysphere.docs @@ -0,0 +1,2 @@ +doc/README +doc/MonkeySpec diff --git a/debian/monkeysphere.install b/debian/monkeysphere.install new file mode 100644 index 0000000..6dd3dda --- /dev/null +++ b/debian/monkeysphere.install @@ -0,0 +1,7 @@ +src/keytrans/openpgp2ssh usr/bin +src/monkeysphere usr/bin +src/monkeysphere-server usr/sbin +src/monkeysphere-ssh-proxycommand usr/bin +src/common usr/share/monkeysphere +etc/monkeysphere.conf etc/monkeysphere +etc/monkeysphere-server.conf etc/monkeysphere diff --git a/debian/monkeysphere.manpages b/debian/monkeysphere.manpages new file mode 100644 index 0000000..a8f6c16 --- /dev/null +++ b/debian/monkeysphere.manpages @@ -0,0 +1,4 @@ +man/man1/monkeysphere.1 +man/man1/openpgp2ssh.1 +man/man1/monkeysphere-ssh-proxycommand.1 +man/man8/monkeysphere-server.8 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..cbe925d --- /dev/null +++ b/debian/rules @@ -0,0 +1,3 @@ +#!/usr/bin/make -f +%: + dh $@ diff --git a/doc/MonkeySpec b/doc/MonkeySpec index 6ee278f..54aaa72 100644 --- a/doc/MonkeySpec +++ b/doc/MonkeySpec @@ -39,9 +39,9 @@ common components server-side components ---------------------- * "howler": server gpg maintainer - - generates gpg keys for the server - - publishes server gpg keys - - used to specify userids to trust for user authentication + - generate gpg keys for the server + - publish server gpg keys + - give owner trust to keys for user authentication * "tamarin": concept - how to trigger or schedule rhesus at admin defined points (e.g. via cron or during ssh connections). diff --git a/doc/README b/doc/README index d8f1897..cda1194 100644 --- a/doc/README +++ b/doc/README @@ -1,36 +1,22 @@ Monkeysphere README =================== -Default files locations (by variable): - -MS_HOME=~/.config/monkeysphere -MS_CONF=$MS_HOME/monkeysphere.conf -AUTH_HOST_FILE=$MS_HOME/auth_host_ids -AUTH_USER_FILE=$MS_HOME/auth_user_ids -GNUPGHOME=~/.gnupg -STAGING_AREA=$MS_HOME - -$STAGING_AREA/host_keys/KEYHASH -$STAGING_AREA/known_hosts -$STAGING_AREA/user_keys/KEYHASH -$STAGING_AREA/authorized_keys - user usage ---------- -For a user to update their ms known_hosts file: +For a user to update their known_hosts file: -$ rhesus --known_hosts +$ monkeysphere update-known_hosts -For a user to update their ms authorized_keys file: +For a user to update their monkeysphere authorized_keys file: -$ rhesus --authorized_keys +$ monkeysphere update-authorized_keys server service publication -------------------------- -To publish a server host key use the "howler" component: +To publish a server host key: -# howler gen-key -# howler publish-key +# monkeysphere-server gen-key +# monkeysphere-server publish-key This will generate the key for server with the service URI (ssh://server.hostname). The server admin should now sign the server @@ -42,38 +28,29 @@ $ gpg --sign-key 'ssh://server.hostname' server authorized_keys maintenance ---------------------------------- -A system can maintain ms authorized_keys files for it's users. Some -different variables need to be defined to help manage this. The way -this is done is by first defining a new MS_HOME: +A system can maintain monkeysphere authorized_keys files for it's +users. -MS_HOME=/etc/monkeysphere - -This directory would then have a monkeysphere.conf which defines the -following variables: +For each user account on the server, the userids of people authorized +to log into that account would be placed in: -AUTH_USER_FILE="$MS_HOME"/auth_user_ids/"$USER" -STAGING_AREA=/var/lib/monkeysphere/stage/$USER -GNUPGHOME=$MS_HOME/gnupg +/etc/monkeysphere/authorized_user_ids/USER -For each user account on the server, the userids of people authorized -to log into that account would be placed in the AUTH_USER_FILE for -that user. However, in order for users to become authenticated, the -server must determine that the user keys have "full" validity. This -means that the server must fully trust at least one person whose -signature on the connecting users key would validate the user. This -would generally be the server admin. If the server admin's keyid is -XXXXXXXX, then on the server run: +However, in order for users to become authenticated, the server must +determine that the user keys have "full" validity. This means that +the server must fully trust at least one person whose signature on the +connecting users key would validate the user. This would generally be +the server admin. If the server admin's keyid is XXXXXXXX, then on +the server run: -# howler trust-key XXXXXXXX +# monkeysphere-server trust-keys XXXXXXXX -To update the ms authorized_keys file for user "bob", the system would -then run the following: +To update the monkeysphere authorized_keys file for user "bob", the +system would then run the following: -# USER=bob MS_HOME=/etc/monkeysphere rhesus --authorized_keys +# monkeysphere-server update-users bob -To update the ms authorized_keys file for all users on the the system: +To update the monkeysphere authorized_keys file for all users on the +the system, run the same command with no arguments: -MS_HOME=/etc/monkeysphere -for USER in $(ls -1 /etc/monkeysphere/auth_user_ids) ; do - rhesus --authorized_keys -done +# monkeysphere-server update-users diff --git a/doc/TODO b/doc/TODO new file mode 100644 index 0000000..4f32bb0 --- /dev/null +++ b/doc/TODO @@ -0,0 +1,124 @@ +Next-Steps Monkeysphere Projects: +--------------------------------- + +Detail advantages of monkeysphere: detail the race conditions in ssh, + and how the monkeysphere can help you reduce these threat vectors: + threat model reduction diagrams. + +Determine how openssh handles multiple processes writing to + known_hosts/authorized_keys files (lockfile, atomic appends?) + +Handle unverified monkeysphere hosts in such a way that they're not + always removed from known_hosts file. Ask user to lsign the host + key? + +Handle multiple hostnames (multiple user IDs?) when generating host + keys with gen-key. + +Work out the details (and describe a full use case) for assigning a + REVOKER during monkeysphere-server gen_key -- how is this set? How + do we export it so it's available when a second-party revocation is + needed? + +Actually enable server hostkey publication. + +Streamline host key generation, publication, verification. See + doc/george/host-key-publication for what dkg went through on + 2008-06-19 + +Ensure that authorized_user_ids are under as tight control as ssh + expects from authorized_keys: we don't want monkeysphere to be a + weak link in the filesystem. + +What happens when a user account has no corresponding + /etc/monkeysphere/authorized_user_ids/$USER file? What gets placed + in /var/cache/monkeysphere/authorized_keys/$USER? It looks + currently untouched, which could mean bad things for such a user. + - if authorized_user_ids is empty, then the user's authorized_keys + file will be also, unless the user-controlled authorized_keys file + is added. I believe this is expected, correct behavior. + +Consider the default permissions for + /var/cache/monkeysphere/authorized_keys/* (and indeed the whole + directory path leading up to that) + +As an administrator, how do i reverse the effect of a + "monkeysphere-server trust-keys" that i later decide i should not + have run? + +Make sure alternate ports are handled for known_hosts. + +Script to import private key into ssh agent. + +Provide a friendly interactive UI for marginal or failing client-side + hostkey verifications. Handle the common cases smoothly, and + provide good debugging info for the unusual cases. + +Make sure onak properly escapes user IDs with colons in them. + +Build a decent, presentable web site for documentation, evangelism, + etc. Include a mention of how to report trouble or concerns. + +Create ssh2openpgp or convert to full-fledged keytrans. + +Resolve the bugs listed in openpgp2ssh(1):BUGS. + +Understand and document alternate trustdb models. + +Understand and document the output of gpg --check-trustdb: + gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model + gpg: depth: 0 valid: 2 signed: 20 trust: 0-, 0q, 0n, 0m, 0f, 2u + gpg: depth: 1 valid: 20 signed: 67 trust: 15-, 0q, 1n, 3m, 1f, 0u + gpg: next trustdb check due at 2008-10-09 + +Understand and document the numeric values between sig! and the keyid + in "gpg --check-sigs $KEYID" . Compare with the details found from + "gpg --with-colons --check-sigs $KEYID". This has to do with trust + signatures. + +Fix gpg's documentation to clarify the difference between validity and + ownertrust. Include better documentation for trust signatures. + +Make it easier to do domain-relative ssh host trust signatures with + gnupg. (e.g. "i trust Jamie McClelland (keyID 76CC057D) to properly + identify ssh servers in the mayfirst.org domain") See: + http://tools.ietf.org/html/rfc4880#section-5.2.3.21 and grep for + "tsign" in gpg(1). + +Fix the order of questions when user does a tsign in gpg or gpg2. + +File bug against ssh-keygen about how "-R" option removes comments + from known_hosts file. + +File bug against ssh-keygen to see if we can get it to write to hash a + known_hosts file to/from stdout/stdin. + +Add environment variables sections to man pages. + +Environment variable scoping. + +Move environment variable precedence before conf file. + +When using ssh-proxycommand, if only host keys found are expired or + revoked, then output loud warning with prompt, or fail hard. + +Update monkeysphere-ssh-proxycommand man page with new keyserver + checking policy info. + +Update monkeysphere-ssh-proxycommand man page with info about + no-connect option. + +File bug against seahorse about how, when creating new primary keys, + it presents option for "RSA (sign only)" but then creates an "esca" + key. + +File bug against enigmail about lack of ability to create subkeys. + +Priviledge separation: monkeysphere user to handle authn keyring and + generate authorized_keys file (which would be moved into place by + root). Host keyring would be owned by root. + +Check permissions of authorized_user_ids file to be writable only by + user and root (same as authorized_keys) + +Improve function that sets owner trust for keys in server keychain. diff --git a/doc/george/changelog b/doc/george/changelog new file mode 100644 index 0000000..aefbf7f --- /dev/null +++ b/doc/george/changelog @@ -0,0 +1,83 @@ +****************************************************************************** +* * +* george system log * +* * +****************************************************************************** +* Please add new entries in reverse chronological order whenever you make * +* changes to this system * +****************************************************************************** + +2008-06-22 - dkg + * installed screen (mjgoins and i were collaborating) + +2008-06-21 - micah + * Restored /etc/init.d/ssh to original package state and changed + /etc/default/ssh to have 'unset SSHD_OOM_ADJUST' instead. + +2008-06-20 - micah + * Commented out the 'export SSHD_OOM_ADJUST=-17' from the + /etc/init.d/ssh initscript, and the 'SSHD_OOM_ADJUST=-17' from + /etc/default/ssh in order to make this error go away: + "error writing /proc/self/oom_adj: Operation not permitted" + (c.f. Debian #487325) + +2008-06-20 - dkg + * touched /etc/environment to get rid of some spurious auth.log + entries. + * turned up sshd's LogLevel from INFO to DEBUG + +2008-06-19 - dkg + * installed rsync (for maintaining a public apt repo) + + * configured mathopd to listen on port 80, serving /srv/www as / + and /srv/apt as /debian. We've got nothing in /srv/www at the + moment, though. + + * installed lsof and psmisc as sysadmin utilities. sorry for the + bloat! + + * installed strace to try to figure out why onak is segfaulting. + +2008-06-19 - dkg + * removed etch sources, switched "testing" to "lenny", added + lenny/updates, removed all contrib and non-free. + + * removed testing pin in /etc/apt/preferences + * ran the upgrade + + * reset emacs22 to emacs22-nox (avoiding dependencies) + + * removed sysklog and klogd because of errors restarting klogd. + Installed syslog-ng in their stead, which still gives errors + related to /proc/kmsg unreadability, but the install completes :/ + + * added experimental + * juggled pinning: experimental: 1, unstable: 2 + * added mathopd onak, tweaked /etc/mathopd.conf and /etc/onak.conf + + * installed monkeysphere v0.1-1, changed host key, published + them via the local keyserver (see host-key-publication) + + * added local unprivileged user accounts for everyone listed in + /usr/share/doc/monkeysphere/copyright + + * configured authorized_user_ids for every user account based on + my best guess at their OpenPGP User ID (see + user-id-configuration). + + * set up a cronjob (in /etc/crontab) to run "monkeysphere-server + update-users" at 26 minutes past the hour. + +2008-06-18 - jrollins + * installed less, emacs; + * aptitude update && aptitude dist-upgrade + +2008-06-18 - micah + * debootstrap'd debian etch install + * installed /etc/apt/sources.list with local proxy sources for etch, + testing, unstable, backports and volatile + * configured /etc/apt/preferences and apt.conf.d/local-conf to + pin etch, but make testing, sid and backports available + * added backports.org apt-key + * installed openssh-server and openssh-client packages + * added dkg, jrollins, mjgoins ssh public_keys to /root/.ssh/authorized_keys diff --git a/doc/george/host-key-publication b/doc/george/host-key-publication new file mode 100644 index 0000000..03e2510 --- /dev/null +++ b/doc/george/host-key-publication @@ -0,0 +1,28 @@ +2008-06-19 02:34:57-0400 +------------------------ + +Adding george's host key to the monkeysphere was more complicated than +it needed to be. + +As the server admin, i did (accepting the defaults where possible): + + monkeysphere-server gen-key + KEYID=$(GNUPGHOME=/etc/monkeysphere/gnupg gpg --with-colons --list-key =ssh://$(hostname --fqdn) | grep ^pub: | cut -f5 -d:) + (umask 077 && GNUPGHOME=/etc/monkeysphere/gnupg gpg --export-secret-key $KEYID | openpgp2ssh $KEYID >/etc/monkeysphere/ssh_host_rsa_key) + # modify /etc/ssh/sshd_config to remove old host keys lines, and + # add new line: HostKey /etc/monkeysphere/ssh_host_rsa_key + /etc/init.d/ssh restart + + KEYSERVER=george.riseup.net monkeysphere-server publish-key + # (needed to publish by hand here because of reasonable sanity checks) + monkeysphere-server show-fingerprint + + # then from a remote host: + gpg --keyserver george.riseup.net --search =ssh://george.riseup.net + gpg --fingerprint --sign-key =ssh://george.riseup.net + KEYID=$(gpg --with-colons --list-key =ssh://george.riseup.net | grep ^pub: | cut -f5 -d:) + gpg --keyserver george.riseup.net --send "$KEYID" + gpg --keyserver george.riseup.net --send "$MYGPGID" + + +How could this have been streamlined? diff --git a/doc/george/policy b/doc/george/policy new file mode 100644 index 0000000..a17a310 --- /dev/null +++ b/doc/george/policy @@ -0,0 +1,33 @@ +Policy for maintaining george.riseup.net +---------------------------------------- + +Riseup graciously provided the MonkeySphere project with a vserver for +testing and public documentation. This is known as george.riseup.net, +for those who are curious about the MonkeySphere. + +george will be maintained as a debian lenny machine, with minimal +packages from experimental as needed for installing and running what +we build elsewhere. + +george will host 3 public-facing services: an ssh daemon on port 22, +an http service on port 80, and an OpenPGP keyserver (the HKP +protocol) on port 11371. + +Administration of george is a shared responsibility across the core +members of the MonkeySphere development team. Administrators will log +changes in their git repositories, in doc/george/changelog (a peer of +this policy file). + +monkeysphere packages installed on george will use unique, tagged +version numbers so we know what we're running. + +We will try to keep the installation as minimal as possible while +still allowing for comfortable day-to-day administration. + +We will use aptitude for package management where possible. + +Outstanding questions: + +Who should have superuser access? + +Who should get regular user accounts? diff --git a/doc/george/user-id-configuration b/doc/george/user-id-configuration new file mode 100644 index 0000000..9a7f4d2 --- /dev/null +++ b/doc/george/user-id-configuration @@ -0,0 +1,40 @@ +2008-06-19 03:00:58-0400 +------------------------ + +setting up authorized_user_id configuration on george was also more +cumbersome than it needs to be. Here's what i (dkg) did: + +monkeysphere-server trust-keys 0EE5BE979282D80B9F7540F1CCD2ED94D21739E9 + +monkeysphere-server update-user-userids dkg 'Daniel Kahn Gillmor ' +monkeysphere-server update-user-userids jrollins 'Jameson Rollins ' +monkeysphere-server update-user-userids micah 'Micah Anderson ' +monkeysphere-server update-user-userids mjgoins 'Matthew Goins ' +monkeysphere-server update-user-userids ross 'Ross Glover ' +monkeysphere-server update-user-userids jamie 'Jamie McClelland ' +monkeysphere-server update-user-userids mlcastle 'mike castleman ' +monkeysphere-server update-user-userids enw 'Elliot Winard ' +monkeysphere-server update-user-userids greg 'Greg Lyle ' + + +then i added a scheduled: + + monkeysphere-server update-users + +to run hourly via /etc/crontab + +and made sure that root's keys were working with a temporary symlink +(see TODO about that business) + +and then modified /etc/ssh/sshd_config with: + + AuthorizedKeysFile /var/cache/monkeysphere/authorized_keys/%u + + +Some outstanding questions: + + * Should we ship a scheduled monkeysphere-server update-users cron + job automatically? + + * why was i not prompted to confirm the trust-keys line, which seems + like the most delicate/sensitive line of all of them? diff --git a/etc/monkeysphere-server.conf b/etc/monkeysphere-server.conf new file mode 100644 index 0000000..847e879 --- /dev/null +++ b/etc/monkeysphere-server.conf @@ -0,0 +1,31 @@ +# MonkeySphere server configuration file. + +# This is an sh-style shell configuration file. Variable names should +# be separated from their assignements by a single '=' and no spaces. + +# GPG home directory for server +#GNUPGHOME=/etc/monkeysphere/gnupg + +# GPG keyserver to search for keys +#KEYSERVER=subkeys.pgp.net + +# Required user key capabilities +# Must be quoted, lowercase, space-seperated list of the following: +# e = encrypt +# s = sign +# c = certify +# a = authentication +#REQUIRED_USER_KEY_CAPABILITY="a" + +# Path to authorized_user_ids file to process to create +# authorized_keys file. '%h' will be replaced by the home directory +# of the user, and %u will be replaced by the username of the user. +# For purely admin-controlled authorized_user_ids, you might put them +# in /etc/monkeysphere/authorized_user_ids/%u +#AUTHORIZED_USER_IDS="%h/.config/monkeysphere/authorized_user_ids" + +# Whether to add user controlled authorized_keys file to +# monkeysphere-generated authorized_keys file. Should be path to file +# where '%h' will be replaced by the home directory of the user. +# To not add any user-controlled file, put "-" +#USER_CONTROLLED_AUTHORIZED_KEYS="%h/.ssh/authorized_keys" diff --git a/etc/monkeysphere.conf b/etc/monkeysphere.conf new file mode 100644 index 0000000..f2ba4a7 --- /dev/null +++ b/etc/monkeysphere.conf @@ -0,0 +1,33 @@ +# MonkeySphere system-wide client configuration file. + +# This is an sh-style shell configuration file. Variable names should +# be separated from their assignements by a single '=' and no spaces. + +# GPG home directory +#GNUPGHOME=~/.gnupg + +# GPG keyserver to search for keys +#KEYSERVER=subkeys.pgp.net + +# Required key capabilities +# Must be quoted, lowercase, space-seperated list of the following: +# e = encrypt +# s = sign +# c = certify +# a = authentication +#REQUIRED_HOST_KEY_CAPABILITY="a" +#REQUIRED_USER_KEY_CAPABILITY="a" + +# ssh known_hosts file +#KNOWN_HOSTS=~/.ssh/known_hosts + +# Whether or not to hash the generated known_hosts lines. +# Should be "true" or "false" +#HASH_KNOWN_HOSTS=true + +# ssh authorized_keys file +#AUTHORIZED_KEYS=~/.ssh/known_hosts + +# This overrides other environment variables +# NOTE: there is leakage +#CHECK_KEYRING=true diff --git a/gpg2ssh/Makefile b/gpg2ssh/Makefile deleted file mode 100644 index 1e816e2..0000000 --- a/gpg2ssh/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -all: monkeysphere gpg2ssh ssh2gpg - -monkeysphere: main.c gnutls-helpers.o - gcc -g -Wall --pedantic -o monkeysphere main.c `libgnutls-config --libs --cflags` -lgnutls-extra gnutls-helpers.o - -gpg2ssh: gpg2ssh.c gnutls-helpers.o - gcc -g -Wall --pedantic -o gpg2ssh gpg2ssh.c `libgnutls-config --libs --cflags` -lgnutls-extra gnutls-helpers.o - -ssh2gpg: ssh2gpg.c gnutls-helpers.o - gcc -g -Wall --pedantic -o ssh2gpg ssh2gpg.c `libgnutls-config --libs --cflags` -lgnutls-extra gnutls-helpers.o - -%.o: %.c - gcc -g -Wall --pedantic -o $@ -c $< - -clean: - rm -f monkeysphere *.o gpg2ssh ssh2gpg - -.PHONY: clean diff --git a/gpg2ssh/gnutls-helpers.c b/gpg2ssh/gnutls-helpers.c deleted file mode 100644 index 6eae29e..0000000 --- a/gpg2ssh/gnutls-helpers.c +++ /dev/null @@ -1,364 +0,0 @@ -/* Author: Daniel Kahn Gillmor */ -/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */ -/* License: GPL v3 or later */ - -#include "gnutls-helpers.h" -/* for htonl() */ -#include - -/* for setlocale() */ -#include - -/* for isalnum() */ -#include - -int loglevel = 0; - - -void err(const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fflush(stderr); -} - -void logfunc(int level, const char* string) { - fprintf(stderr, "GnuTLS Logging (%d): %s\n", level, string); -} - -void init_keyid(gnutls_openpgp_keyid_t keyid) { - memset(keyid, 'x', sizeof(gnutls_openpgp_keyid_t)); -} - - - -void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid) -{ - static const char hex[16] = "0123456789ABCDEF"; - unsigned int kix = 0, outix = 0; - - while (kix < sizeof(gnutls_openpgp_keyid_t)) { - out[outix] = hex[(keyid[kix] >> 4) & 0x0f]; - out[outix + 1] = hex[keyid[kix] & 0x0f]; - kix++; - outix += 2; - } -} - - -int init_gnutls() { - const char* version = NULL; - const char* debug_string = NULL; - int ret; - - if (ret = gnutls_global_init(), ret) { - err("Failed to do gnutls_global_init() (error: %d)\n", ret); - return 1; - } - - version = gnutls_check_version(NULL); - - if (version) - err("gnutls version: %s\n", version); - else { - err("no version found!\n"); - return 1; - } - - if (debug_string = getenv("MONKEYSPHERE_DEBUG"), debug_string) { - loglevel = atoi(debug_string); - gnutls_global_set_log_function(logfunc); - - gnutls_global_set_log_level(loglevel); - err("set log level to %d\n", loglevel); - } - return 0; -} - -void init_datum(gnutls_datum_t* d) { - d->data = NULL; - d->size = 0; -} -void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src) { - dest->data = gnutls_realloc(dest->data, src->size); - dest->size = src->size; - memcpy(dest->data, src->data, src->size); -} -int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b) { - if (a->size > b->size) { - err("a is larger\n"); - return 1; - } - if (a->size < b->size) { - err("b is larger\n"); - return -1; - } - return memcmp(a->data, b->data, a->size); -} -void free_datum(gnutls_datum_t* d) { - gnutls_free(d->data); - d->data = NULL; - d->size = 0; -} - -/* read the passed-in string, store in a single datum */ -int set_datum_string(gnutls_datum_t* d, const char* s) { - unsigned int x = strlen(s)+1; - unsigned char* c = NULL; - - c = gnutls_realloc(d->data, x); - if (NULL == c) - return -1; - d->data = c; - d->size = x; - memcpy(d->data, s, x); - return 0; -} - -/* read the passed-in file descriptor until EOF, store in a single - datum */ -int set_datum_fd(gnutls_datum_t* d, int fd) { - unsigned int bufsize = 1024; - unsigned int len = 0; - - FILE* f = fdopen(fd, "r"); - if (bufsize > d->size) { - bufsize = 1024; - d->data = gnutls_realloc(d->data, bufsize); - if (d->data == NULL) { - err("out of memory!\n"); - return -1; - } - d->size = bufsize; - } else { - bufsize = d->size; - } - f = fdopen(fd, "r"); - if (NULL == f) { - err("could not fdopen FD %d\n", fd); - } - clearerr(f); - while (!feof(f) && !ferror(f)) { - if (len == bufsize) { - /* allocate more space by doubling: */ - bufsize *= 2; - d->data = gnutls_realloc(d->data, bufsize); - if (d->data == NULL) { - err("out of memory!\n"); - return -1; - }; - d->size = bufsize; - } - len += fread(d->data + len, 1, bufsize - len, f); - /* err("read %d bytes\n", len); */ - } - if (ferror(f)) { - err("Error reading from fd %d (error: %d) (error: %d '%s')\n", fd, ferror(f), errno, strerror(errno)); - return -1; - } - - /* touch up buffer size to match reality: */ - d->data = gnutls_realloc(d->data, len); - d->size = len; - return 0; -} - -/* read the file indicated (by name) in the fname parameter. store - its entire contents in a single datum. */ -int set_datum_file(gnutls_datum_t* d, const char* fname) { - struct stat sbuf; - unsigned char* c = NULL; - FILE* file = NULL; - size_t x = 0; - - if (0 != stat(fname, &sbuf)) { - err("failed to stat '%s'\n", fname); - return -1; - } - - c = gnutls_realloc(d->data, sbuf.st_size); - if (NULL == c) { - err("failed to allocate %d bytes for '%s'\n", sbuf.st_size, fname); - return -1; - } - - d->data = c; - d->size = sbuf.st_size; - file = fopen(fname, "r"); - if (NULL == file) { - err("failed to open '%s' for reading\n", fname); - return -1; - } - - x = fread(d->data, d->size, 1, file); - if (x != 1) { - err("tried to read %d bytes, read %d instead from '%s'\n", d->size, x, fname); - fclose(file); - return -1; - } - fclose(file); - return 0; -} - -int write_datum_fd(int fd, const gnutls_datum_t* d) { - if (d->size != write(fd, d->data, d->size)) { - err("failed to write body of datum.\n"); - return -1; - } - return 0; -} - - -int write_datum_fd_with_length(int fd, const gnutls_datum_t* d) { - uint32_t len; - int looks_negative = (d->data[0] & 0x80); - unsigned char zero = 0; - - /* if the first bit is 1, then the datum will appear negative in the - MPI encoding style used by OpenSSH. In that case, we'll increase - the length by one, and dump out one more byte */ - - if (looks_negative) { - len = htonl(d->size + 1); - } else { - len = htonl(d->size); - } - if (write(fd, &len, sizeof(len)) != sizeof(len)) { - err("failed to write size of datum.\n"); - return -2; - } - if (looks_negative) { - if (write(fd, &zero, 1) != 1) { - err("failed to write padding byte for MPI.\n"); - return -2; - } - } - return write_datum_fd(fd, d); -} - -int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num) { - unsigned int i; - int ret; - - for (i = 0; i < num; i++) - if (ret = write_datum_fd_with_length(fd, d[i]), ret != 0) - return ret; - - return 0; -} - - -int datum_from_string(gnutls_datum_t* d, const char* str) { - d->size = strlen(str); - d->data = gnutls_realloc(d->data, d->size); - if (d->data == 0) - return ENOMEM; - memcpy(d->data, str, d->size); - return 0; -} - - -int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]) { - int p[2]; - int ret; - - if (pid == NULL) { - err("bad pointer passed to create_writing_pipe()\n"); - return -1; - } - - if (ret = pipe(p), ret == -1) { - err("failed to create a pipe (error: %d \"%s\")\n", errno, strerror(errno)); - return -1; - } - - *pid = fork(); - if (*pid == -1) { - err("Failed to fork (error: %d \"%s\")\n", errno, strerror(errno)); - return -1; - } - if (*pid == 0) { /* this is the child */ - close(p[1]); /* close unused write end */ - - if (0 != dup2(p[0], 0)) { /* map the reading end into stdin */ - err("Failed to transfer reading file descriptor to stdin (error: %d \"%s\")\n", errno, strerror(errno)); - exit(1); - } - execv(path, argv); - err("exec %s failed (error: %d \"%s\")\n", path, errno, strerror(errno)); - /* close the open file descriptors */ - close(p[0]); - close(0); - - exit(1); - } else { /* this is the parent */ - close(p[0]); /* close unused read end */ - return p[1]; - } -} - -int validate_ssh_host_userid(const char* userid) { - char* oldlocale = setlocale(LC_ALL, "C"); - - /* choke if userid does not match the expected format - ("ssh://fully.qualified.domain.name") */ - if (strncmp("ssh://", userid, strlen("ssh://")) != 0) { - err("The user ID should start with ssh:// for a host key\n"); - goto fail; - } - /* so that isalnum will work properly */ - userid += strlen("ssh://"); - while (0 != (*userid)) { - if (!isalnum(*userid)) { - err("label did not start with a letter or a digit! (%s)\n", userid); - goto fail; - } - userid++; - while (isalnum(*userid) || ('-' == (*userid))) - userid++; - if (('.' == (*userid)) || (0 == (*userid))) { /* clean end of label: - check last char - isalnum */ - if (!isalnum(*(userid - 1))) { - err("label did not end with a letter or a digit!\n"); - goto fail; - } - if ('.' == (*userid)) /* advance to the start of the next label */ - userid++; - } else { - err("invalid character in domain name: %c\n", *userid); - goto fail; - } - } - /* ensure that the last character is valid: */ - if (!isalnum(*(userid - 1))) { - err("hostname did not end with a letter or a digit!\n"); - goto fail; - } - /* FIXME: fqdn's can be unicode now, thanks to RFC 3490 -- how do we - make sure that we've got an OK string? */ - - return 0; - - fail: - setlocale(LC_ALL, oldlocale); - return 1; -} - -/* http://tools.ietf.org/html/rfc4880#section-5.5.2 */ -size_t get_openpgp_mpi_size(gnutls_datum_t* d) { - return 2 + d->size; -} - -int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d) { - uint16_t x; - - x = d->size * 8; - x = htons(x); - - write(fd, &x, sizeof(x)); - write(fd, d->data, d->size); - - return 0; -} diff --git a/gpg2ssh/gnutls-helpers.h b/gpg2ssh/gnutls-helpers.h deleted file mode 100644 index 9ea22a3..0000000 --- a/gpg2ssh/gnutls-helpers.h +++ /dev/null @@ -1,72 +0,0 @@ -/* Author: Daniel Kahn Gillmor */ -/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */ -/* License: GPL v3 or later */ - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Functions to help dealing with GnuTLS for monkeysphere key - translation projects: */ - -/* set everything up, including logging levels. Return 0 on - success */ -int init_gnutls(); - -/* logging and output functions: */ - -void err(const char* fmt, ...); -void logfunc(int level, const char* string); - -/* basic datum manipulations: */ - -void init_datum(gnutls_datum_t* d); -void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src); -int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b); -void free_datum(gnutls_datum_t* d); -int write_datum_fd(int fd, const gnutls_datum_t* d); -int write_datum_fd_with_length(int fd, const gnutls_datum_t* d); -int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num); - -/* set up a datum from a null-terminated string */ -int datum_from_string(gnutls_datum_t* d, const char* str); - -/* keyid manipulations: */ -typedef unsigned char printable_keyid[16]; - -void init_keyid(gnutls_openpgp_keyid_t keyid); -void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid); - -/* functions to get data into datum objects: */ - -/* read the passed-in string, store in a single datum */ -int set_datum_string(gnutls_datum_t* d, const char* s); - -/* read the passed-in file descriptor until EOF, store in a single - datum */ -int set_datum_fd(gnutls_datum_t* d, int fd); - -/* read the file indicated (by name) in the fname parameter. store - its entire contents in a single datum. */ -int set_datum_file(gnutls_datum_t* d, const char* fname); - -/* set up file descriptor pipe for writing (child process pid gets - stored in pid, fd is returned)*/ -int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]); - -/* return 0 if userid matches the monkeysphere spec for ssh host user IDs */ -int validate_ssh_host_userid(const char* userid); - -/* how many bytes will it take to write out this datum in OpenPGP MPI form? */ -size_t get_openpgp_mpi_size(gnutls_datum_t* d); - -/* write the MPI stored in gnutls_datum_t to file descriptor fd: */ -int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d); diff --git a/gpg2ssh/gpg2ssh.c b/gpg2ssh/gpg2ssh.c deleted file mode 100644 index a1e94df..0000000 --- a/gpg2ssh/gpg2ssh.c +++ /dev/null @@ -1,291 +0,0 @@ -#include "gnutls-helpers.h" - -#include -#include - -/* for waitpid() */ -#include -#include - -/* - Author: Daniel Kahn Gillmor - Date: Tue, 08 Apr 2008 - License: GPL v3 or later - - monkeysphere public key translator: execute this with an GPG - certificate (public key(s) + userid(s)) on stdin. It currently - only works with RSA keys. - - It will spit out a version of the first key capable of being used - for authentication on stdout. The output format should be suitable - for appending a known_hosts file. - - Requirements: I've only built this so far with GnuTLS v2.3.4 -- - version 2.2.0 does not contain the appropriate pieces. - - */ - -int main(int argc, char* argv[]) { - gnutls_datum_t data; - int ret; - gnutls_openpgp_crt_t openpgp_crt; - gnutls_openpgp_keyid_t keyid; - printable_keyid p_keyid; - unsigned int keyidx; - unsigned int usage, bits; - gnutls_pk_algorithm_t algo; - - gnutls_datum_t m, e, p, q, g, y; - gnutls_datum_t algolabel; - - char output_data[10240]; - char userid[10240]; - size_t uidsz = sizeof(userid); - - const gnutls_datum_t* all[5]; - int pipefd; - pid_t child_pid; - char* const args[] = {"/usr/bin/base64", "--wrap=0", NULL}; - const char* algoname; - int mpicount; - int pipestatus; - - init_gnutls(); - - init_datum(&data); - - init_datum(&m); - init_datum(&e); - init_datum(&p); - init_datum(&q); - init_datum(&g); - init_datum(&y); - - init_datum(&algolabel); - - init_keyid(keyid); - - /* slurp in the private key from stdin */ - if (ret = set_datum_fd(&data, 0), ret) { - err("didn't read file descriptor 0\n"); - return 1; - } - - - if (ret = gnutls_openpgp_crt_init(&openpgp_crt), ret) { - err("Failed to initialize OpenPGP certificate (error: %d)\n", ret); - return 1; - } - - /* format could be either: GNUTLS_OPENPGP_FMT_RAW, - GNUTLS_OPENPGP_FMT_BASE64; if MONKEYSPHERE_RAW is set, use RAW, - otherwise, use BASE64: */ - - /* FIXME: we should be auto-detecting the input format, and - translating it as needed. */ - - if (getenv("MONKEYSPHERE_RAW")) { - err("assuming RAW formatted certificate\n"); - if (ret = gnutls_openpgp_crt_import(openpgp_crt, &data, GNUTLS_OPENPGP_FMT_RAW), ret) { - err("failed to import the OpenPGP certificate in RAW format (error: %d)\n", ret); - return ret; - } - } else { - err("assuming BASE64 formatted certificate\n"); - if (ret = gnutls_openpgp_crt_import (openpgp_crt, &data, GNUTLS_OPENPGP_FMT_BASE64), ret) { - err("failed to import the OpenPGP certificate in BASE64 format (error: %d)\n", ret); - return ret; - } - } - - if (gnutls_openpgp_crt_get_revoked_status(openpgp_crt)) { - err("the primary key was revoked!\n"); - return 1; - } - - /* FIXME: We're currently looking at the primary key or maybe the - first authentication-capable subkey. - - Instead, we should be iterating through the primary key and all - subkeys: for each one with the authentication usage flag set of a - algorithm we can handle, we should output matching UserIDs and - the SSH version of the key. */ - - - if (ret = gnutls_openpgp_crt_get_key_usage(openpgp_crt, &usage), ret) { - err("failed to get the usage flags for the primary key (error: %d)\n", ret); - return ret; - } - if (usage & GNUTLS_KEY_KEY_AGREEMENT) { - err("the primary key can be used for authentication\n"); - - algo = gnutls_openpgp_crt_get_pk_algorithm(openpgp_crt, &bits); - if (algo < 0) { - err("failed to get the algorithm of the OpenPGP public key (error: %d)\n", algo); - return algo; - } else if (algo == GNUTLS_PK_RSA) { - - err("OpenPGP RSA certificate, with %d bits\n", bits); - ret = gnutls_openpgp_crt_get_pk_rsa_raw(openpgp_crt, &m, &e); - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to export RSA key parameters (error: %d)\n", ret); - return 1; - } - } else if (algo == GNUTLS_PK_DSA) { - err("OpenPGP DSA Key, with %d bits\n", bits); - ret = gnutls_openpgp_crt_get_pk_dsa_raw(openpgp_crt, &p, &q, &g, &y); - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to export DSA key parameters (error: %d)\n", ret); - return 1; - } - } else { - err("OpenPGP Key was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", algo); - return 1; - } - - } else { - err("primary key is only good for: 0x%08x. Trying subkeys...\n", usage); - - if (ret = gnutls_openpgp_crt_get_auth_subkey(openpgp_crt, keyid, 0), ret) { - err("failed to find a subkey capable of authentication (error: %d)\n", ret); - return ret; - } - make_keyid_printable(p_keyid, keyid); - err("found authentication subkey %.16s\n", p_keyid); - - ret = gnutls_openpgp_crt_get_subkey_idx(openpgp_crt, keyid); - if (ret < 0) { - err("could not get the index of subkey %.16s (error: %d)\n", ret); - return ret; - } - keyidx = ret; - - if (gnutls_openpgp_crt_get_subkey_revoked_status(openpgp_crt, keyidx)) { - err("The authentication subkey was revoked!\n"); - return 1; - } - - if (ret = gnutls_openpgp_crt_get_subkey_usage(openpgp_crt, keyidx, &usage), ret) { - err("could not figure out usage of subkey %.16s (error: %d)\n", p_keyid, ret); - return ret; - } - if ((usage & GNUTLS_KEY_KEY_AGREEMENT) == 0) { - err("could not find a subkey with authentication privileges.\n"); - return 1; - } - - /* switch, based on the algorithm in question, to extract the MPI - components: */ - - algo = gnutls_openpgp_crt_get_subkey_pk_algorithm(openpgp_crt, keyidx, &bits); - if (algo < 0) { - err("failed to get the algorithm of the authentication subkey (error: %d)\n", algo); - return algo; - } else if (algo == GNUTLS_PK_RSA) { - - err("OpenPGP RSA subkey, with %d bits\n", bits); - ret = gnutls_openpgp_crt_get_subkey_pk_rsa_raw(openpgp_crt, keyidx, &m, &e); - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to export RSA subkey parameters (error: %d)\n", ret); - return 1; - } - } else if (algo == GNUTLS_PK_DSA) { - err("OpenPGP DSA subkey, with %d bits\n", bits); - ret = gnutls_openpgp_crt_get_subkey_pk_dsa_raw(openpgp_crt, keyidx, &p, &q, &g, &y); - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to export DSA subkey parameters (error: %d)\n", ret); - return 1; - } - } else { - err("OpenPGP subkey was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", algo); - return 1; - } - } - - /* make sure userid is NULL-terminated */ - userid[sizeof(userid) - 1] = 0; - uidsz--; - - /* FIXME: we're just choosing the first UserID from the certificate: - instead, we should be selecting every User ID that is adequately - signed and matches the spec, and aggregating them with commas for - known_hosts output */ - - if (ret = gnutls_openpgp_crt_get_name(openpgp_crt, 0, userid, &uidsz), ret) { - err("Failed to fetch the first UserID (error: %d)\n", ret); - return ret; - } - - if (ret = validate_ssh_host_userid(userid), ret) { - err("bad userid: not a valid ssh host.\n"); - return ret; - } - - /* remove ssh:// from the beginning of userid */ - memmove(userid, userid + strlen("ssh://"), 1 + strlen(userid) - strlen("ssh://")); - - - /* now we have algo, and the various MPI data are set. Can we - export them cleanly? */ - - /* for the moment, we'll just dump the info raw, and pipe it - externally through coreutils' /usr/bin/base64 */ - - if (algo == GNUTLS_PK_RSA) { - algoname = "ssh-rsa"; - mpicount = 3; - - all[0] = &algolabel; - all[1] = &e; - all[2] = &m; - } else if (algo == GNUTLS_PK_DSA) { - algoname = "ssh-dss"; - mpicount = 5; - - all[0] = &algolabel; - all[1] = &p; - all[2] = &q; - all[3] = &g; - all[4] = &y; - } else { - err("no idea what this algorithm is: %d\n", algo); - return 1; - } - - if (ret = datum_from_string(&algolabel, algoname), ret) { - err("couldn't label string (error: %d)\n", ret); - return ret; - } - - snprintf(output_data, sizeof(output_data), "%s %s ", userid, algoname); - - pipefd = create_writing_pipe(&child_pid, args[0], args); - if (pipefd < 0) { - err("failed to create a writing pipe (returned %d)\n", pipefd); - return pipefd; - } - - write(1, output_data, strlen(output_data)); - - if (0 != write_data_fd_with_length(pipefd, all, mpicount)) { - err("was not able to write out RSA key data\n"); - return 1; - } - close(pipefd); - if (child_pid != waitpid(child_pid, &pipestatus, 0)) { - err("could not wait for child process to return for some reason.\n"); - return 1; - } - if (pipestatus != 0) { - err("base64 pipe died with return code %d\n", pipestatus); - return pipestatus; - } - - write(1, "\n", 1); - - - - gnutls_openpgp_crt_deinit(openpgp_crt); - gnutls_global_deinit(); - return 0; -} diff --git a/gpg2ssh/main.c b/gpg2ssh/main.c deleted file mode 100644 index d6bac68..0000000 --- a/gpg2ssh/main.c +++ /dev/null @@ -1,271 +0,0 @@ -#include "gnutls-helpers.h" - -#include -#include - -/* - Author: Daniel Kahn Gillmor - Date: Tue, 01 Apr 2008 - License: GPL v3 or later - - monkeysphere private key translator: execute this with an GPG - secret key on stdin (at the moment, only passphraseless RSA keys - work). - - It will spit out a PEM-encoded version of the key on stdout, which - can be fed into ssh-add like this: - - gpg --export-secret-keys $KEYID | monkeysphere | ssh-add -c /dev/stdin - - Requirements: I've only built this so far with GnuTLS v2.3.4 -- - version 2.2.0 does not contain the appropriate pieces. - - Notes: gpgkey2ssh doesn't seem to provide the same public - keys. Mighty weird! - -0 wt215@squeak:~/monkeysphere$ gpg --export-secret-keys 1DCDF89F | ~dkg/src/monkeysphere/monkeysphere | ssh-add -c /dev/stdin -gnutls version: 2.3.4 -OpenPGP RSA Key, with 1024 bits -Identity added: /dev/stdin (/dev/stdin) -The user has to confirm each use of the key -0 wt215@squeak:~/monkeysphere$ ssh-add -L -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC9gWQqfrnhQKDQnND/3eOexpddE64J+1zp9fcyCje7H5LKclb6DBV2HS6WgW32PJhIzvP+fYZM3dzXea3fpv14y1SicXiRBDgF9SnsNA1qWn2RyzkLcKy7PmM0PDYtU1oiLTcQj/xkWcqW2sLKHT/WW+vZP5XP7RMGN/yWNMfE2Q== /dev/stdin -0 wt215@squeak:~/monkeysphere$ gpgkey2ssh 1DCDF89F -ssh-rsa AAAAB3NzaC1yc2EAAACBAL2BZCp+ueFAoNCc0P/d457Gl10Trgn7XOn19zIKN7sfkspyVvoMFXYdLpaBbfY8mEjO8/59hkzd3Nd5rd+m/XjLVKJxeJEEOAX1Kew0DWpafZHLOQtwrLs+YzQ8Ni1TWiItNxCP/GRZypbawsodP9Zb69k/lc/tEwY3/JY0x8TZAAAAAwEAAQ== COMMENT -0 wt215@squeak:~/monkeysphere$ - - */ - - -int convert_pgp_to_x509(gnutls_x509_privkey_t* output, gnutls_datum_t* input) { - gnutls_openpgp_privkey_t pgp_privkey; - gnutls_datum_t m, e, d, p, q, u, g, y, x; - gnutls_pk_algorithm_t pgp_algo; - unsigned int pgp_bits; - int ret; - - init_datum(&m); - init_datum(&e); - init_datum(&d); - init_datum(&p); - init_datum(&q); - init_datum(&u); - init_datum(&g); - init_datum(&y); - init_datum(&x); - - if (ret = gnutls_openpgp_privkey_init(&pgp_privkey), ret) { - err("Failed to initialized OpenPGP private key (error: %d)\n", ret); - return 1; - } - - - /* format could be either: GNUTLS_OPENPGP_FMT_RAW, - GNUTLS_OPENPGP_FMT_BASE64; if MONKEYSPHERE_RAW is set, use RAW, - otherwise, use BASE64: */ - - if (getenv("MONKEYSPHERE_RAW")) { - err("assuming RAW formatted private keys\n"); - if (ret = gnutls_openpgp_privkey_import(pgp_privkey, input, GNUTLS_OPENPGP_FMT_RAW, NULL, 0), ret) - err("failed to import the OpenPGP private key in RAW format (error: %d)\n", ret); - } else { - err("assuming BASE64 formatted private keys\n"); - if (ret = gnutls_openpgp_privkey_import (pgp_privkey, input, GNUTLS_OPENPGP_FMT_BASE64, NULL, 0), ret) - err("failed to import the OpenPGP private key in BASE64 format (error: %d)\n", ret); - } - - pgp_algo = gnutls_openpgp_privkey_get_pk_algorithm(pgp_privkey, &pgp_bits); - if (pgp_algo < 0) { - err("failed to get OpenPGP key algorithm (error: %d)\n", pgp_algo); - return 1; - } - if (pgp_algo == GNUTLS_PK_RSA) { - err("OpenPGP RSA Key, with %d bits\n", pgp_bits); - ret = gnutls_openpgp_privkey_export_rsa_raw(pgp_privkey, &m, &e, &d, &p, &q, &u); - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to export RSA key parameters (error: %d)\n", ret); - return 1; - } - - ret = gnutls_x509_privkey_import_rsa_raw (*output, &m, &e, &d, &p, &q, &u); - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to import RSA key parameters (error: %d)\n", ret); - return 1; - } - } else if (pgp_algo == GNUTLS_PK_DSA) { - err("OpenPGP DSA Key, with %d bits\n", pgp_bits); - ret = gnutls_openpgp_privkey_export_dsa_raw(pgp_privkey, &p, &q, &g, &y, &x); - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to export DSA key parameters (error: %d)\n", ret); - return 1; - } - - ret = gnutls_x509_privkey_import_dsa_raw (*output, &p, &q, &g, &y, &x); - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to import DSA key parameters (error: %d)\n", ret); - return 1; - } - } else { - err("OpenPGP Key was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", pgp_algo); - return 1; - } - - ret = gnutls_x509_privkey_fix(*output); - if (ret != 0) { - err("failed to fix up the private key in X.509 format (error: %d)\n", ret); - return 1; - } - - gnutls_openpgp_privkey_deinit(pgp_privkey); - return 0; -} - -int convert_x509_to_pgp(gnutls_openpgp_privkey_t* output, gnutls_datum_t* input) { - gnutls_x509_privkey_t x509_privkey; - gnutls_datum_t m, e, d, p, q, u, g, y, x; - gnutls_pk_algorithm_t x509_algo; - int ret; - - init_datum(&m); - init_datum(&e); - init_datum(&d); - init_datum(&p); - init_datum(&q); - init_datum(&u); - init_datum(&g); - init_datum(&y); - init_datum(&x); - - if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) { - err("Failed to initialized X.509 private key (error: %d)\n", ret); - return 1; - } - - - /* format could be either: GNUTLS_X509_FMT_DER, - GNUTLS_X509_FMT_PEM; if MONKEYSPHERE_DER is set, use DER, - otherwise, use PEM: */ - - if (getenv("MONKEYSPHERE_DER")) { - err("assuming DER formatted private keys\n"); - if (ret = gnutls_x509_privkey_import(x509_privkey, input, GNUTLS_X509_FMT_DER), ret) - err("failed to import the X.509 private key in DER format (error: %d)\n", ret); - } else { - err("assuming PEM formatted private keys\n"); - if (ret = gnutls_x509_privkey_import (x509_privkey, input, GNUTLS_X509_FMT_PEM), ret) - err("failed to import the X.509 private key in PEM format (error: %d)\n", ret); - } - - x509_algo = gnutls_x509_privkey_get_pk_algorithm(x509_privkey); - if (x509_algo < 0) { - err("failed to get X.509 key algorithm (error: %d)\n", x509_algo); - return 1; - } - if (x509_algo == GNUTLS_PK_RSA) { - err("X.509 RSA Key\n"); - ret = gnutls_x509_privkey_export_rsa_raw(x509_privkey, &m, &e, &d, &p, &q, &u); - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to export RSA key parameters (error: %d)\n", ret); - return 1; - } - - /* ret = gnutls_openpgp_privkey_import_rsa_raw (*output, &m, &e, &d, &p, &q, &u); */ - ret = GNUTLS_E_UNIMPLEMENTED_FEATURE; - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to import RSA key parameters (error: %d)\n", ret); - return 1; - } - } else if (x509_algo == GNUTLS_PK_DSA) { - err("X.509 DSA Key\n"); - ret = gnutls_x509_privkey_export_dsa_raw(x509_privkey, &p, &q, &g, &y, &x); - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to export DSA key parameters (error: %d)\n", ret); - return 1; - } - - /* ret = gnutls_openpgp_privkey_import_dsa_raw (*output, &p, &q, &g, &y, &x); */ - ret = GNUTLS_E_UNIMPLEMENTED_FEATURE; - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to import DSA key parameters (error: %d)\n", ret); - return 1; - } - } else { - err("OpenPGP Key was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", x509_algo); - return 1; - } - - gnutls_x509_privkey_deinit(x509_privkey); - return 0; -} - - -int main(int argc, char* argv[]) { - gnutls_datum_t data; - int ret; - gnutls_x509_privkey_t x509_privkey; - - char output_data[10240]; - size_t ods = sizeof(output_data); - - init_gnutls(); - - init_datum(&data); - - /* slurp in the private key from stdin */ - if (ret = set_datum_fd(&data, 0), ret) { - err("didn't read file descriptor 0\n"); - return 1; - } - - - - /* Or, instead, read in key from a file name: - if (ret = set_datum_file(&data, argv[1]), ret) { - err("didn't read file '%s'\n", argv[1]); - return 1; - } -*/ - - /* treat the passed file as an X.509 private key, and extract its - component values: */ - -/* if (ret = gnutls_x509_privkey_import(x509_privkey, &data, GNUTLS_X509_FMT_PEM), ret) { */ -/* err("Failed to import the X.509 key (error: %d)\n", ret); */ -/* return 1; */ -/* } */ -/* gnutls_x509_privkey_export_rsa_raw(x509_privkey, &m, &e, &d, &p, &q, &u); */ - - /* try to print the PEM-encoded private key: */ -/* ret = gnutls_x509_privkey_export (x509_privkey, */ -/* GNUTLS_X509_FMT_PEM, */ -/* output_data, */ -/* &ods); */ -/* printf("ret: %u; ods: %u;\n", ret, ods); */ -/* if (ret == 0) { */ -/* write(0, output_data, ods); */ -/* } */ - - - if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) { - err("Failed to initialize X.509 private key (error: %d)\n", ret); - return 1; - } - - if (ret = convert_pgp_to_x509(&x509_privkey, &data), ret) { - return ret; - } - - ret = gnutls_x509_privkey_export (x509_privkey, - GNUTLS_X509_FMT_PEM, - output_data, - &ods); - printf("ret: %u; ods: %u;\n", ret, ods); - if (ret == 0) { - write(1, output_data, ods); - } - - - gnutls_x509_privkey_deinit(x509_privkey); - gnutls_global_deinit(); - return 0; -} diff --git a/gpg2ssh/ssh2gpg.c b/gpg2ssh/ssh2gpg.c deleted file mode 100644 index b14a540..0000000 --- a/gpg2ssh/ssh2gpg.c +++ /dev/null @@ -1,171 +0,0 @@ -#include "gnutls-helpers.h" - -#include -#include - -/* for waitpid() */ -#include -#include - -/* for time() */ -#include - -/* for htons() */ -#include - - -/* - Author: Daniel Kahn Gillmor - Date: Sun, 2008-04-20 - License: GPL v3 or later - - monkeysphere public key translator: execute this with an ssh - private key on stdin. It currently only works with RSA keys. - - it should eventually work with OpenSSH-style public keys instead of - the full private key, but it was easier to do this way. - - It shoud spit out a version of the public key suitable for acting - as an OpenPGP public sub key packet. - - */ - -int main(int argc, char* argv[]) { - gnutls_datum_t data; - int ret; - gnutls_x509_privkey_t x509_privkey; - gnutls_openpgp_crt_t openpgp_crt; - gnutls_openpgp_keyid_t keyid; - printable_keyid p_keyid; - unsigned int keyidx; - unsigned int usage, bits; - gnutls_pk_algorithm_t algo; - - unsigned char packettag; - unsigned char openpgpversion; - time_t timestamp; - uint32_t clunkytime; - unsigned char openpgpalgo; - unsigned int packetlen; - uint16_t plen; - - gnutls_datum_t m, e, d, p, q, u, g, y; - gnutls_datum_t algolabel; - - char output_data[10240]; - char userid[10240]; - size_t uidsz = sizeof(userid); - - const gnutls_datum_t* all[5]; - int pipefd; - pid_t child_pid; - char* const args[] = {"/usr/bin/base64", "--wrap=0", NULL}; - const char* algoname; - int mpicount; - int pipestatus; - - init_gnutls(); - - init_datum(&data); - - init_datum(&m); - init_datum(&e); - init_datum(&d); - init_datum(&p); - init_datum(&q); - init_datum(&u); - init_datum(&g); - init_datum(&y); - - init_datum(&algolabel); - - init_keyid(keyid); - - /* slurp in the private key from stdin */ - if (ret = set_datum_fd(&data, 0), ret) { - err("didn't read file descriptor 0\n"); - return 1; - } - - - if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) { - err("Failed to initialize private key structure (error: %d)\n", ret); - return 1; - } - - err("assuming PEM formatted private key\n"); - if (ret = gnutls_x509_privkey_import(x509_privkey, &data, GNUTLS_X509_FMT_PEM), ret) { - err("failed to import the PEM-encoded private key (error: %d)\n", ret); - return ret; - } - - algo = gnutls_x509_privkey_get_pk_algorithm(x509_privkey); - if (algo < 0) { - err("failed to get the algorithm of the PEM-encoded public key (error: %d)\n", algo); - return algo; - } else if (algo == GNUTLS_PK_RSA) { - err("RSA private key\n"); - ret = gnutls_x509_privkey_export_rsa_raw(x509_privkey, &m, &e, &d, &p, &q, &u); - if (GNUTLS_E_SUCCESS != ret) { - err ("failed to export RSA key parameters (error: %d)\n", ret); - return 1; - } - err("Modulus size %d, exponent size %d\n", m.size, e.size); - } else if (algo == GNUTLS_PK_DSA) { - err("DSA Key, not implemented!!\n", bits); - return 1; - } else { - err("Key was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", algo); - return 1; - } - - /* now we have algo, and the various MPI data are set. Can we - export them as a public subkey packet? */ - - /* this packet should be tagged 14, and should contain: - - 1 octet: version (4) - 4 octets: time of generation (seconds since 1970) - 1 octet: algo (http://tools.ietf.org/html/rfc4880#section-5.5.2 implies 1 for RSA) - - MPI: modulus - MPI: exponent - */ - - packetlen = 1 + 4 + 1; - /* FIXME: this is RSA only. for DSA, there'll be more: */ - packetlen += get_openpgp_mpi_size(&m) + get_openpgp_mpi_size(&e); - - /* FIXME: we should generate this bound more cleanly -- i just - happen to know that 65535 is 2^16-1: */ - if (packetlen > 65535) { - err("packet length is too long (%d)\n", packetlen); - return 1; - } - - /* we're going to emit an old-style packet, with tag 14 (public - subkey), with a two-octet packet length */ - packettag = 0x80 | (14 << 2) | 1; - - write(1, &packettag, sizeof(packettag)); - plen = htons(packetlen); - write(1, &plen, sizeof(plen)); - - openpgpversion = 4; - write(1, &openpgpversion, 1); - - timestamp = time(NULL); - clunkytime = htonl(timestamp); - write(1, &clunkytime, 4); - - /* FIXME: handle things other than RSA */ - openpgpalgo = 1; - write(1, &openpgpalgo, 1); - - write_openpgp_mpi_to_fd(1, &m); - write_openpgp_mpi_to_fd(1, &e); - - gnutls_x509_privkey_deinit(x509_privkey); - gnutls_global_deinit(); - return 0; -} diff --git a/howler/howler b/howler/howler deleted file mode 100755 index 0b67c02..0000000 --- a/howler/howler +++ /dev/null @@ -1,134 +0,0 @@ -#!/bin/sh - -# howler: monkeysphere server gpg generator/publisher/maintainer -# -# Written by -# Jameson Rollins -# -# Copyright 2008, released under the GPL, version 3 or later - -PGRM=$(basename $0) - -######################################################################## -# FUNCTIONS -######################################################################## - -usage() { -cat <&2 - exit ${2:-'1'} -} - -# generate server gpg key -gen_key() { - KEY_TYPE=${KEY_TYPE:-RSA} - KEY_LENGTH=${KEY_LENGTH:-2048} - KEY_USAGE=${KEY_USAGE:-encrypt,auth} - SERVICE=${SERVICE:-ssh} - HOSTNAME_FQDN=${HOSTNAME_FQDN:-$(hostname -f)} - - USERID=${USERID:-"$SERVICE"://"$HOSTNAME_FQDN"} - - echo "key parameters:" - cat < /dev/null 2>&1 ; then - failure "key for '$USERID' already exists" - fi - - echo "generating server key..." - gpg --batch --gen-key < /dev/null | grep '^pub:' | cut -d: -f5) - - # dummy command so as not to publish fakes keys during testing - # eventually: - #gpg --send-keys --keyserver "$KEYSERVER" "$keyID" - echo "gpg --send-keys --keyserver $KEYSERVER $keyID" -} - -trust_key() { - for keyID ; do - # get the key from the key server - gpg --keyserver "$KEYSERVER" --recv-key "$keyID" || failure "could not retrieve key '$keyID'" - - # edit the key to change trust - # FIXME: need to figure out how to automate this, - # in a batch mode or something. - gpg --edit-key "$keyID" - done -} - -######################################################################## -# MAIN -######################################################################## - -# set ms home directory -MS_HOME=${MS_HOME:-/etc/monkeysphere} - -# load configuration file -MS_CONF=${MS_CONF:-"$MS_HOME"/monkeysphere.conf} -[ -e "$MS_CONF" ] && . "$MS_CONF" - -GNUPGHOME=${GNUPGHOME:-"$MS_HOME"/gnupg} -export GNUPGHOME -KEYSERVER=${KEYSERVER:-subkeys.pgp.net} -export KEYSERVER - -COMMAND="$1" -[ "$COMMAND" ] || failure "Type '$PGRM help' for usage." -shift 1 - -case $COMMAND in - 'gen-key') - gen_key - ;; - 'publish-key') - publish_key - ;; - 'trust-key') - if [ -z "$1" ] ; then - failure "you must specify at least one key to trust." - fi - trust_key "$@" - ;; - 'help') - usage - exit - ;; - *) - failure "Unknown command: '$COMMAND' -Type '$PGRM help' for usage." - ;; -esac diff --git a/langur/README b/langur/README deleted file mode 100644 index ee60701..0000000 --- a/langur/README +++ /dev/null @@ -1,4 +0,0 @@ -Langur is the policy editor/viewer for the monkeysphere. - -Its goals are to provide a human-friendly interface to the simple and -intelligible policies monkeysphere supports. diff --git a/man/man1/monkeysphere-ssh-proxycommand.1 b/man/man1/monkeysphere-ssh-proxycommand.1 new file mode 100644 index 0000000..c4196f2 --- /dev/null +++ b/man/man1/monkeysphere-ssh-proxycommand.1 @@ -0,0 +1,53 @@ +.TH MONKEYSPHERE-SSH-PROXYCOMMAND "1" "June 2008" "monkeysphere 0.1" "User Commands" + +.SH NAME + +monkeysphere-ssh-proxycommand \- MonkeySphere ssh ProxyCommand script + +.SH DESCRIPTION + +\fBmonkeysphere-ssh-proxy\fP is an ssh proxy command that can be used +to trigger a monkeysphere update of the known_hosts file for the hosts +that are being connected to. It is meant to be run as an ssh +ProxyCommand. This can either be done by specifying the proxy command +on the command line: + +.B ssh -o ProxyCommand="monkeysphere-ssh-proxycommand %h %p" ... + +or by adding the following line to your ~/.ssh/config script: + +.B ProxyCommand monkeysphere-ssh-proxycommand %h %p + +The script can easily be incorporated into other ProxyCommand scripts +by calling it with the "--no-connect" option, ie: + +.B monkeysphere-ssh-proxycommand --no-connect "$HOST" "$PORT" + +This will run everything but will not exec netcat to make the tcp +connection to the host. + +.SH KEYSERVER CHECKING + +The proxy command has a fairly nuanced policy for when keyservers are +queried when processing host. If the host userID is not found in +either the user's keyring or in the known_hosts file, then the +keyserver is queried for the host userID. If the host userID is found +in the user's keyring, then the keyserver is not checked. This is +because... If the host userID is not found in the user's keyring, but +the host is listed in the known_hosts file, then defered check is +scheduled. + +.SH ENVIRONMENT VARIABLES + +.TP +KEYSERVER The keyserver to query. + +.SH AUTHOR + +Written by Jameson Rollins + +.SH SEE ALSO + +.BR monkeypshere (1), +.BR ssh (1), +.BR gpg (1) diff --git a/man/man1/monkeysphere.1 b/man/man1/monkeysphere.1 new file mode 100644 index 0000000..30e35bb --- /dev/null +++ b/man/man1/monkeysphere.1 @@ -0,0 +1,94 @@ +.TH MONKEYSPHERE "1" "June 2008" "monkeysphere 0.1" "User Commands" + +.SH NAME + +monkeysphere \- MonkeySphere client user interface + +.SH SYNOPSIS + +.B monkeysphere \fIcommand\fP [\fIargs\fP] + +.SH DESCRIPTION + +MonkeySphere is a system to leverage the OpenPGP Web of Trust for ssh +authentication and encryption. OpenPGP keys are tracked via GnuPG, +and added to the ssh authorized_keys and known_hosts files to be used +for authentication of ssh connections. + +\fBmonkeysphere\fP is the MonkeySphere client utility. + +.SH SUBCOMMANDS + +\fBmonkeysphere\fP takes various subcommands: +.TP +.B update-known_hosts [HOST]... +Update the known_hosts file. For each specified host, gpg will be +queried for a key associated with the host URI (see HOST URIs), +querying a keyserver if specified. If a key is found, it will be +converted to an ssh key, and any matching ssh keys will be removed +from the user's known_hosts file. If the found key is acceptable (see +KEY ACCEPTABILITY), then the key will be updated and re-added to the +known_hosts file. If no gpg key is found for the host, then nothing +is done. If no hosts are specified, all hosts listed in the +known_hosts file will be processed. `k' may be used in place of +`update-known_hosts'. +.TP +.B update-authorized_keys +Update the monkeysphere authorized_keys file. For each user ID in the +user's authorized_user_ids file, gpg will be queried for keys +associated with that user ID, querying a keyserver if specified. If a +key is found, it will be converted to an ssh key, and any matching ssh +keys will be removed from the user's authorized_keys file. If the +found key is acceptable (see KEY ACCEPTABILITY), then the key will be +updated and re-added to the authorized_keys file. If no gpg key is +found for the user ID, then nothing is done. `a' may be used in place +of `update-authorized_keys'. +.TP +.B gen-subkey KEYID +Generate an `a` capable subkey. For the primary key with the +specified key ID, generate a subkey with "authentication" capability +that can be used for MonkeySphere transactions. `g' may be used in +place of `gen-subkey'. +.TP +.B help +Output a brief usage summary. `h' or `?' may be used in place of +`help'. + +.SH HOST URIs + +Host OpenPGP keys have associated user IDs that use the ssh URI +specification for the host, ie. "ssh://host.full.domain". + +.SH KEY ACCEPTABILITY + +GPG keys are considered acceptable if the following criteria are met: +.TP +.B capability +The key must have the "authentication" ("a") usage flag set. +.TP +.B validity +The key must be "fully" valid, and must not be expired or revoked. + +.SH FILES + +.TP +~/.config/monkeysphere/monkeysphere.conf +User monkeysphere config file. +.TP +/etc/monkeysphere/monkeysphere.conf +System-wide monkeysphere config file. +.TP +~/.config/monkeysphere/authorized_user_ids +OpenPGP user IDs associated with keys that will be checked for +addition to the authorized_keys file. + +.SH AUTHOR + +Written by Jameson Rollins + +.SH SEE ALSO + +.BR monkeysphere-ssh-proxycommand (1), +.BR monkeysphere-server (8), +.BR ssh (1), +.BR gpg (1) diff --git a/man/man1/openpgp2ssh.1 b/man/man1/openpgp2ssh.1 new file mode 100644 index 0000000..bea1da5 --- /dev/null +++ b/man/man1/openpgp2ssh.1 @@ -0,0 +1,91 @@ +.\" -*- nroff -*- +.Dd $Mdocdate: June 11, 2008 $ +.Dt OPENPGP2SSH 1 +.Os +.Sh NAME +openpgp2ssh +.Nd translate OpenPGP keys to SSH keys +.Sh SYNOPSIS +.Nm openpgp2ssh < mykey.gpg +.Pp +.Nm gpg --export $KEYID | openpgp2ssh $KEYID +.Pp +.Nm gpg --export-secret-key $KEYID | openpgp2ssh $KEYID +.Sh DESCRIPTION +.Nm +takes an OpenPGP-formatted primary key and associated +subkeys on standard input, and spits out the requested equivalent +SSH-style key on standard output. +.Pp +If the data on standard input contains no subkeys, you can invoke +.Nm +without arguments. If the data on standard input contains +multiple keys (e.g. a primary key and associated subkeys), you must +specify a specific OpenPGP keyid (e.g. CCD2ED94D21739E9) or +fingerprint as the first argument to indicate which key to export. +The keyid must be exactly 16 hex characters. +.Pp +If the input contains an OpenPGP RSA or DSA public key, it will be +converted to the OpenSSH-style single-line keystring, prefixed with +the key type. This format is suitable (with minor alterations) for +insertion into known_hosts files and authorized_keys files. +.Pp +If the input contains an OpenPGP RSA or DSA secret key, it will be +converted to the equivalent PEM-encoded private key. +.Pp +.Nm +is part of the +.Xr monkeysphere 1 +framework for providing a PKI for SSH. +.Sh CAVEATS +The keys produced by this process are stripped of all identifying +information, including certifications, self-signatures, etc. This is +intentional, since ssh attaches no inherent significance to these +features. +.Pp +.Nm +only works with RSA or DSA keys, because those are the +only ones which work with ssh. +.Pp +Assuming a valid key type, though, +.Nm +will produce output for +any requested key. This means, among other things, that it will +happily export revoked keys, unverifiable keys, expired keys, etc. +Make sure you do your own key validation before using this tool! +.Sh EXAMPLES +.Nm gpg --export-secret-key $KEYID | openpgp2ssh $KEYID | ssh-add -c /dev/stdin +.Pp +This pushes the secret key into the active +.Xr ssh-agent 1 . +Tools such as +.Xr ssh 1 +which know how to talk to the +.Xr ssh-agent 1 +can now rely on the key. +.Sh AUTHOR +.Nm +and this man page were written by Daniel Kahn Gillmor +. +.Sh BUGS +.Nm +Currently only exports into formats used by the OpenSSH. +It should support other key output formats, such as those used by +lsh(1) and putty(1). +.Pp +Secret key output is currently not passphrase-protected. +.Pp +.Nm +currently cannot handle passphrase-protected secret keys on input. +.Pp +It would be nice to be able to use keyids shorter or longer than 16 +hex characters. +.Pp +.Nm +only acts on keys associated with the first primary key +passed in. If you send it more than one primary key, it will silently +ignore later ones. +.Sh SEE ALSO +.Xr monkeysphere 1 , +.Xr ssh 1 , +.Xr monkeysphere-server 8 diff --git a/man/man8/monkeysphere-server.8 b/man/man8/monkeysphere-server.8 new file mode 100644 index 0000000..2b5af5e --- /dev/null +++ b/man/man8/monkeysphere-server.8 @@ -0,0 +1,102 @@ +.TH MONKEYSPHERE-SERVER "1" "June 2008" "monkeysphere 0.1" "User Commands" + +.SH NAME + +monkeysphere-server \- monkeysphere server admin user interface + +.SH SYNOPSIS + +.B monkeysphere-server \fIcommand\fP [\fIargs\fP] + +.SH DESCRIPTION + +\fBMonkeySphere\fP is a system to leverage the OpenPGP Web of Trust +for ssh authentication and encryption. OpenPGP keys are tracked via +GnuPG, and added to the ssh authorized_keys and known_hosts files to +be used for authentication of ssh connections. + +\fBmonkeysphere-server\fP is the MonkeySphere server admin utility. + +.SH SUBCOMMANDS + +\fBmonkeysphere-server\fP takes various subcommands: +.TP +.B update-users [USER]... +Update the admin-controlled authorized_keys files for user. For each +user specified, user ID's listed in the user's authorized_user_ids +file are processed, and the user's authorized_keys file in +/var/cache/monkeysphere/authorized_keys/USER. See `man monkeysphere' +for more info. If the USER_CONTROLLED_AUTHORIZED_KEYS variable is +set, then a user-controlled authorized_keys file (usually +~USER/.ssh/authorized_keys) is added to the authorized_keys file. `k' +may be used in place of `update-known_hosts'. +.TP +.B gen-key +Generate a gpg key for the host. `g' may be used in place of +`gen-key'. +.TP +.B show-fingerprint +Show the fingerprint for the host's OpenPGP key. `f' may be used in place of +`show-fingerprint'. +.TP +.B publish-key +Publish the host's gpg key to the keyserver. `p' may be used in place +of `publish-key'. +.TP +.B trust-keys KEYID... +Mark key specified with key IDs with full owner trust. `t' may be used +in place of `trust-keys'. +.TP +.B help +Output a brief usage summary. `h' or `?' may be used in place of +`help'. + +.SH SETUP + +In order to start using the monkeysphere, there are a couple of things +you need to do first. The first is to generate an OpenPGP key for the +server and convert that key to an ssh key that can be used by ssh for +host authentication. To do this, run the "gen-key" subcommand. Once +that is done, publish the key to a keyserver with "publish-key" +subcommand. Finally, you need to modify the sshd_config to tell sshd +where the new server host key: + +HostKey /etc/monkeysphere/ssh_host_rsa_key + +If the server will also handle user authentication through +monkeysphere-generated authorized_keys files, set the following: + +AuthorizedKeysFile /var/cache/monkeysphere/authorized_keys/%u + +Once those changes are made, restart the ssh server. + +.SH FILES + +.TP +/etc/monkeysphere/monkeysphere-server.conf +System monkeysphere-server config file. +.TP +/etc/monkeysphere/monkeysphere.conf +System-wide monkeysphere config file. +.TP +/etc/monkeysphere/gnupg +Monkeysphere GNUPG home directory. +.TP +/etc/monkeysphere/ssh_host_rsa_key +Copy of the host's private key in ssh format, suitable for use by sshd. +.TP +/etc/monkeysphere/authorized_user_ids/USER +Server maintained authorized_user_ids files for users. +.TP +/var/cache/monkeysphere/authorized_keys/USER +User authorized_keys file. + +.SH AUTHOR + +Written by Jameson Rollins + +.SH SEE ALSO + +.BR monkeysphere (1), +.BR gpg (1), +.BR ssh (1) diff --git a/monkeysphere.conf b/monkeysphere.conf deleted file mode 100644 index cd5e3b2..0000000 --- a/monkeysphere.conf +++ /dev/null @@ -1,15 +0,0 @@ -# monkeysphere system configuration file - -# This is particular configuration is meant to be sourced by the -# rhesus shell script when run in administrative mode to maintain -# authorized_keys files for users. - -AUTH_USER_FILE=/etc/monkeysphere/auth_user_ids/"$USER" - -STAGING_AREA=/var/lib/monkeysphere/stage/"$USER" - -# gpg home directory for server -GNUPGHOME=/etc/monkeysphere/gnupg - -# gpg keyserver to search for keys -KEYSERVER=subkeys.pgp.net diff --git a/rhesus/README b/rhesus/README deleted file mode 100644 index 4d383d5..0000000 --- a/rhesus/README +++ /dev/null @@ -1,30 +0,0 @@ -rhesus is the monkeysphere authorized_keys/known_hosts generator. - -In authorized_keys mode, rhesus takes an auth_user_ids file, which -contains gpg user ids, uses gpg to fetch the keys of the specified -users, does a monkeysphere policy check on each id, and uses gpg2ssh -to generate authorized_keys lines for each verified id. The lines are -then combined with a user's traditional authorized_keys file to create -a new authorized_keys file. - -In known_hosts mode, rhesus takes an auth_host_ids file, which -contains gpg user ids of the form ssh://URL, uses gpg to fetch the -keys of the specified hosts, does a monkeysphere policy check on each -id, and uses gpg2ssh to generate a known_hosts lines for each verified -id. The lines are then combined with a user's traditional known_hosts -file to create a new known_hosts file. - -When run as a normal user, no special configuration is needed. - -When run as an administrator to update system-maintained -authorized_keys files for each user, the following environment -variables should be defined first: - - MS_CONF=/etc/monkeysphere/monkeysphere.conf - USER=foo - -For example, the command might be run like this: - - for USER in $(ls -1 /home) ; do - MS_CONF=/etc/monkeysphere/monkeysphere.conf rhesus --authorized_keys - done diff --git a/rhesus/rhesus b/rhesus/rhesus deleted file mode 100755 index 7a43fca..0000000 --- a/rhesus/rhesus +++ /dev/null @@ -1,323 +0,0 @@ -#!/bin/sh -e - -# rhesus: monkeysphere authorized_keys/known_hosts generating script -# -# Written by -# Jameson Rollins -# -# Copyright 2008, released under the GPL, version 3 or later - -PGRM=$(basename $0) - -######################################################################## -# FUNCTIONS -######################################################################## - -usage() { -cat <&2 - exit ${2:-'1'} -} - -log() { - echo -n "ms: " - echo "$@" -} - -# cut out all comments(#) and blank lines from standard input -meat() { - grep -v -e "^[[:space:]]*#" -e '^$' -} - -# cut a specified line from standard input -cutline() { - head --line="$1" | tail -1 -} - -# retrieve all keys with given user id from keyserver -# FIXME: need to figure out how to retrieve all matching keys -# (not just first 5) -gpg_fetch_keys() { - local id - id="$1" - echo 1,2,3,4,5 | \ - gpg --quiet --batch --command-fd 0 --with-colons \ - --keyserver "$KEYSERVER" \ - --search ="$id" >/dev/null 2>&1 -} - -# convert escaped characters from gpg output back into original -# character -# FIXME: undo all escape character translation in with-colons gpg output -unescape() { - echo "$1" | sed 's/\\x3a/:/' -} - -# stand in until we get dkg's gpg2ssh program -gpg2ssh_tmp() { - local mode - local keyID - - mode="$1" - keyID="$2" - userID="$3" - - if [ "$mode" = 'authorized_keys' -o "$mode" = 'a' ] ; then - gpgkey2ssh "$keyID" | sed -e "s/COMMENT/$userID/" - elif [ "$mode" = 'known_hosts' -o "$mode" = 'k' ] ; then - echo -n "$userID "; gpgkey2ssh "$keyID" | sed -e 's/ COMMENT//' - fi -} - -# userid and key policy checking -# the following checks policy on the returned keys -# - checks that full key has appropriate valididy (u|f) -# - checks key has appropriate capability (E|A) -# - checks that particular desired user id has appropriate validity -# see /usr/share/doc/gnupg/DETAILS.gz -# FIXME: add some more status output -# expects global variable: "mode" -process_user_id() { - local userID - local cacheDir - local keyOK - local keyCapability - local keyFingerprint - local userIDHash - - userID="$1" - cacheDir="$2" - - # fetch all keys from keyserver - # if none found, break - if ! gpg_fetch_keys "$userID" ; then - echo " no keys found." - return - fi - - # some crazy piping here that takes the output of gpg and - # pipes it into a "while read" loop that reads each line - # of standard input one-by-one. - gpg --fixed-list-mode --list-key --with-colons \ - --with-fingerprint ="$userID" 2> /dev/null | \ - cut -d : -f 1,2,5,10,12 | \ - while IFS=: read -r type validity keyid uidfpr capability ; do - # process based on record type - case $type in - 'pub') - # new key, wipe the slate - keyOK= - keyCapability= - keyFingerprint= - # check primary key validity - if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then - continue - fi - # check capability is not Disabled... - if echo "$capability" | grep -q 'D' ; then - continue - fi - # check capability is Encryption and Authentication - # FIXME: make more flexible capability specification - # (ie. in conf file) - if echo "$capability" | grep -q -v 'E' ; then - if echo "$capability" | grep -q -v 'A' ; then - continue - fi - fi - keyCapability="$capability" - keyOK=true - keyID="$keyid" - ;; - 'fpr') - # if key ok, get fingerprint - if [ "$keyOK" ] ; then - keyFingerprint="$uidfpr" - fi - ;; - 'uid') - # check key ok and we have key fingerprint - if [ -z "$keyOK" -o -z "$keyFingerprint" ] ; then - continue - fi - # check key validity - if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then - continue - fi - # check the uid matches - if [ "$(unescape "$uidfpr")" != "$userID" ] ; then - continue - fi - # convert the key - # FIXME: needs to apply extra options if specified - echo -n " valid key found; generating ssh key(s)... " - userIDHash=$(echo "$userID" | sha1sum | awk '{ print $1 }') - # export the key with gpg2ssh - #gpg --export "$keyFingerprint" | gpg2ssh "$mode" > "$cacheDir"/"$userIDHash"."$keyFingerprint" - # stand in until we get dkg's gpg2ssh program - gpg2ssh_tmp "$mode" "$keyID" "$userID" > "$cacheDir"/"$userIDHash"."$keyFingerprint" - if [ "$?" = 0 ] ; then - echo "done." - else - echo "error." - fi - ;; - esac - done -} - -# process the auth_*_ids file -# go through line-by-line, extracting and processing each user id -# expects global variable: "mode" -process_auth_file() { - local authIDsFile - local cacheDir - local nLines - local line - local userID - - authIDsFile="$1" - cacheDir="$2" - - # find number of user ids in auth_user_ids file - nLines=$(meat <"$authIDsFile" | wc -l) - - # clean out keys file and remake keys directory - rm -rf "$cacheDir" - mkdir -p "$cacheDir" - - # loop through all user ids - for line in $(seq 1 $nLines) ; do - # get user id - # FIXME: needs to handle extra options if necessary - userID=$(meat <"$authIDsFile" | cutline "$line" ) - - # process the user id and extract keys - log "processing user id: '$userID'" - process_user_id "$userID" "$cacheDir" - done -} - -######################################################################## -# MAIN -######################################################################## - -if [ -z "$1" ] ; then - usage - exit 1 -fi - -# check mode -mode="$1" -shift 1 - -# check user -if ! id -u "$USER" > /dev/null 2>&1 ; then - failure "invalid user '$USER'." -fi - -# set user home directory -HOME=$(getent passwd "$USER" | cut -d: -f6) - -# set ms home directory -MS_HOME=${MS_HOME:-"$HOME"/.config/monkeysphere} - -# load configuration file -MS_CONF=${MS_CONF:-"$MS_HOME"/monkeysphere.conf} -[ -e "$MS_CONF" ] && . "$MS_CONF" - -# set config variable defaults -STAGING_AREA=${STAGING_AREA:-"$MS_HOME"} -AUTH_HOST_FILE=${AUTH_HOST_FILE:-"$MS_HOME"/auth_host_ids} -AUTH_USER_FILE=${AUTH_USER_FILE:-"$MS_HOME"/auth_user_ids} -GNUPGHOME=${GNUPGHOME:-"$HOME"/.gnupg} -KEYSERVER=${KEYSERVER:-subkeys.pgp.net} - -USER_KNOW_HOSTS="$HOME"/.ssh/known_hosts -USER_AUTHORIZED_KEYS="$HOME"/.ssh/authorized_keys - -# export USER and GNUPGHOME variables, since they are used by gpg -export USER -export GNUPGHOME - -# stagging locations -hostKeysCacheDir="$STAGING_AREA"/host_keys -userKeysCacheDir="$STAGING_AREA"/user_keys -msKnownHosts="$STAGING_AREA"/known_hosts -msAuthorizedKeys="$STAGING_AREA"/authorized_keys - -# set mode variables -if [ "$mode" = 'known_hosts' -o "$mode" = 'k' ] ; then - fileType=known_hosts - authFileType=auth_host_ids - authIDsFile="$AUTH_HOST_FILE" - outFile="$msKnownHosts" - cacheDir="$hostKeysCacheDir" - userFile="$USER_KNOWN_HOSTS" -elif [ "$mode" = 'authorized_keys' -o "$mode" = 'a' ] ; then - fileType=authorized_keys - authFileType=auth_user_ids - authIDsFile="$AUTH_USER_FILE" - outFile="$msAuthorizedKeys" - cacheDir="$userKeysCacheDir" - userFile="$USER_AUTHORIZED_KEYS" -else - failure "unknown command '$mode'." -fi - -# check auth ids file -if [ ! -s "$authIDsFile" ] ; then - echo "'$authFileType' file is empty or does not exist." - exit -fi - -log "user '$USER': monkeysphere $fileType generation" - -# make sure gpg home exists with proper permissions -mkdir -p -m 0700 "$GNUPGHOME" - -# if users are specified on the command line, process just -# those users -if [ "$1" ] ; then - # process userids given on the command line - for userID ; do - if ! grep -q "$userID" "$authIDsFile" ; then - log "userid '$userID' not in $authFileType file." - continue - fi - log "processing user id: '$userID'" - process_user_id "$userID" "$cacheDir" - done -# otherwise if no users are specified, process the entire -# auth_*_ids file -else - # process the auth file - process_auth_file "$authIDsFile" "$cacheDir" -fi - -# write output key file -log "writing ms $fileType file... " -> "$outFile" -if [ "$(ls "$cacheDir")" ] ; then - log -n "adding gpg keys... " - cat "$cacheDir"/* > "$outFile" - echo "done." -else - log "no gpg keys to add." -fi -if [ -s "$userFile" ] ; then - log -n "adding user $fileType file... " - cat "$userFile" >> "$outFile" - echo "done." -fi -log "ms $fileType file generated:" -log "$outFile" diff --git a/src/common b/src/common new file mode 100644 index 0000000..b220150 --- /dev/null +++ b/src/common @@ -0,0 +1,571 @@ +# -*-shell-script-*- + +# Shared sh functions for the monkeysphere +# +# Written by +# Jameson Rollins +# +# Copyright 2008, released under the GPL, version 3 or later + +# all-caps variables are meant to be user supplied (ie. from config +# file) and are considered global + +######################################################################## +### COMMON VARIABLES + +# managed directories +ETC="/etc/monkeysphere" +export ETC +CACHE="/var/cache/monkeysphere" +export CACHE +ERR=0 +export ERR + +######################################################################## +### UTILITY FUNCTIONS + +error() { + log "$1" + ERR=${2:-'1'} +} + +failure() { + echo "$1" >&2 + exit ${2:-'1'} +} + +# write output to stderr +log() { + echo -n "ms: " >&2 + echo "$@" >&2 +} + +loge() { + echo "$@" >&2 +} + +# cut out all comments(#) and blank lines from standard input +meat() { + grep -v -e "^[[:space:]]*#" -e '^$' +} + +# cut a specified line from standard input +cutline() { + head --line="$1" | tail -1 +} + +# check that characters are in a string (in an AND fashion). +# used for checking key capability +# check_capability capability a [b...] +check_capability() { + local usage + local capcheck + + usage="$1" + shift 1 + + for capcheck ; do + if echo "$usage" | grep -q -v "$capcheck" ; then + return 1 + fi + done + return 0 +} + +# convert escaped characters from gpg output back into original +# character +# FIXME: undo all escape character translation in with-colons gpg output +unescape() { + echo "$1" | sed 's/\\x3a/:/' +} + +# remove all lines with specified string from specified file +remove_line() { + local file + local string + + file="$1" + string="$2" + + if [ "$file" -a "$string" ] ; then + grep -v "$string" "$file" | sponge "$file" + fi +} + +# translate ssh-style path variables %h and %u +translate_ssh_variables() { + local uname + local home + + uname="$1" + path="$2" + + # get the user's home directory + userHome=$(getent passwd "$uname" | cut -d: -f6) + + # translate '%u' to user name + path=${path/\%u/"$uname"} + # translate '%h' to user home directory + path=${path/\%h/"$userHome"} + + echo "$path" +} + +### CONVERTION UTILITIES + +# output the ssh key for a given key ID +gpg2ssh() { + local keyID + + #keyID="$1" #TMP + # only use last 16 characters until openpgp2ssh can take all 40 #TMP + keyID=$(echo "$1" | cut -c 25-) #TMP + + gpg --export "$keyID" | openpgp2ssh "$keyID" 2> /dev/null +} + +# output the ssh key for a given secret key ID +gpgsecret2ssh() { + local keyID + + #keyID="$1" #TMP + # only use last 16 characters until openpgp2ssh can take all 40 #TMP + keyID=$(echo "$1" | cut -c 25-) #TMP + + gpg --export-secret-key "$keyID" | openpgp2ssh "$keyID" 2> /dev/null +} + +# output known_hosts line from ssh key +ssh2known_hosts() { + local host + local key + + host="$1" + key="$2" + + echo -n "$host " + echo -n "$key" | tr -d '\n' + echo " MonkeySphere${DATE}" +} + +# output authorized_keys line from ssh key +ssh2authorized_keys() { + local userID + local key + + userID="$1" + key="$2" + + echo -n "$key" | tr -d '\n' + echo " MonkeySphere${DATE} ${userID}" +} + +# convert key from gpg to ssh known_hosts format +gpg2known_hosts() { + local host + local keyID + + host="$1" + keyID="$2" + + # NOTE: it seems that ssh-keygen -R removes all comment fields from + # all lines in the known_hosts file. why? + # NOTE: just in case, the COMMENT can be matched with the + # following regexp: + # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$' + echo -n "$host " + gpg2ssh "$keyID" | tr -d '\n' + echo " MonkeySphere${DATE}" +} + +# convert key from gpg to ssh authorized_keys format +gpg2authorized_keys() { + local userID + local keyID + + userID="$1" + keyID="$2" + + # NOTE: just in case, the COMMENT can be matched with the + # following regexp: + # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$' + gpg2ssh "$keyID" | tr -d '\n' + echo " MonkeySphere${DATE} ${userID}" +} + +### GPG UTILITIES + +# retrieve all keys with given user id from keyserver +# FIXME: need to figure out how to retrieve all matching keys +# (not just first N (5 in this case)) +gpg_fetch_userid() { + local userID + + userID="$1" + + log -n " checking keyserver $KEYSERVER... " + echo 1,2,3,4,5 | \ + gpg --quiet --batch --with-colons \ + --command-fd 0 --keyserver "$KEYSERVER" \ + --search ="$userID" > /dev/null 2>&1 + loge "done." +} + +# get the full fingerprint of a key ID +get_key_fingerprint() { + local keyID + + keyID="$1" + + gpg --list-key --with-colons --fixed-list-mode \ + --with-fingerprint --with-fingerprint "$keyID" | \ + grep '^fpr:' | grep "$keyID" | cut -d: -f10 +} + +######################################################################## +### PROCESSING FUNCTIONS + +# userid and key policy checking +# the following checks policy on the returned keys +# - checks that full key has appropriate valididy (u|f) +# - checks key has specified capability (REQUIRED_*_KEY_CAPABILITY) +# - checks that requested user ID has appropriate validity +# (see /usr/share/doc/gnupg/DETAILS.gz) +# output is one line for every found key, in the following format: +# +# flag fingerprint +# +# "flag" is an acceptability flag, 0 = ok, 1 = bad +# "fingerprint" is the fingerprint of the key +# +# expects global variable: "MODE" +process_user_id() { + local userID + local requiredCapability + local requiredPubCapability + local gpgOut + local type + local validity + local keyid + local uidfpr + local usage + local keyOK + local uidOK + local lastKey + local lastKeyOK + local fingerprint + + userID="$1" + + # set the required key capability based on the mode + if [ "$MODE" = 'known_hosts' ] ; then + requiredCapability="$REQUIRED_HOST_KEY_CAPABILITY" + elif [ "$MODE" = 'authorized_keys' ] ; then + requiredCapability="$REQUIRED_USER_KEY_CAPABILITY" + fi + requiredPubCapability=$(echo "$requiredCapability" | tr "[:lower:]" "[:upper:]") + + # if CHECK_KEYSERVER variable set, check the keyserver + # for the user ID + if [ "$CHECK_KEYSERVER" = "true" ] ; then + gpg_fetch_userid "$userID" + fi + + # output gpg info for (exact) userid and store + gpgOut=$(gpg --list-key --fixed-list-mode --with-colon \ + --with-fingerprint --with-fingerprint \ + ="$userID" 2>/dev/null) + + # if the gpg query return code is not 0, return 1 + if [ "$?" -ne 0 ] ; then + log " - key not found." + return 1 + fi + + # loop over all lines in the gpg output and process. + # need to do it this way (as opposed to "while read...") so that + # variables set in loop will be visible outside of loop + echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \ + while IFS=: read -r type validity keyid uidfpr usage ; do + # process based on record type + case $type in + 'pub') # primary keys + # new key, wipe the slate + keyOK= + uidOK= + lastKey=pub + lastKeyOK= + fingerprint= + + log " primary key found: $keyid" + + # if overall key is not valid, skip + if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then + log " - unacceptable primary key validity ($validity)." + continue + fi + # if overall key is disabled, skip + if check_capability "$usage" 'D' ; then + log " - key disabled." + continue + fi + # if overall key capability is not ok, skip + if ! check_capability "$usage" $requiredPubCapability ; then + log " - unacceptable primary key capability ($usage)." + continue + fi + + # mark overall key as ok + keyOK=true + + # mark primary key as ok if capability is ok + if check_capability "$usage" $requiredCapability ; then + lastKeyOK=true + fi + ;; + 'uid') # user ids + # if an acceptable user ID was already found, skip + if [ "$uidOK" ] ; then + continue + fi + # if the user ID does not match, skip + if [ "$(unescape "$uidfpr")" != "$userID" ] ; then + continue + fi + # if the user ID validity is not ok, skip + if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then + continue + fi + + # mark user ID acceptable + uidOK=true + + # output a line for the primary key + # 0 = ok, 1 = bad + if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then + log " * acceptable key found." + echo 0 "$fingerprint" + else + echo 1 "$fingerprint" + fi + ;; + 'sub') # sub keys + # unset acceptability of last key + lastKey=sub + lastKeyOK= + fingerprint= + + # if sub key validity is not ok, skip + if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then + continue + fi + # if sub key capability is not ok, skip + if ! check_capability "$usage" $requiredCapability ; then + continue + fi + + # mark sub key as ok + lastKeyOK=true + ;; + 'fpr') # key fingerprint + fingerprint="$uidfpr" + + # if the last key was the pub key, skip + if [ "$lastKey" = pub ] ; then + continue + fi + + # output a line for the last subkey + # 0 = ok, 1 = bad + if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then + log " * acceptable key found." + echo 0 "$fingerprint" + else + echo 1 "$fingerprint" + fi + ;; + esac + done +} + +# process hosts in the known_host file +process_hosts_known_hosts() { + local host + local userID + local ok + local keyid + local tmpfile + + # create a lockfile on known_hosts + lockfile-create "$KNOWN_HOSTS" + + for host ; do + log "processing host: $host" + + userID="ssh://${host}" + + process_user_id "ssh://${host}" | \ + while read -r ok keyid ; do + sshKey=$(gpg2ssh "$keyid") + # remove the old host key line + remove_line "$KNOWN_HOSTS" "$sshKey" + # if key OK, add new host line + if [ "$ok" -eq '0' ] ; then + # hash if specified + if [ "$HASH_KNOWN_HOSTS" = 'true' ] ; then + # FIXME: this is really hackish cause ssh-keygen won't + # hash from stdin to stdout + tmpfile=$(mktemp) + ssh2known_hosts "$host" "$sshKey" > "$tmpfile" + ssh-keygen -H -f "$tmpfile" 2> /dev/null + cat "$tmpfile" >> "$KNOWN_HOSTS" + rm -f "$tmpfile" "${tmpfile}.old" + else + ssh2known_hosts "$host" "$sshKey" >> "$KNOWN_HOSTS" + fi + fi + done + # touch the lockfile, for good measure. + lockfile-touch --oneshot "$KNOWN_HOSTS" + done + + # remove the lockfile + lockfile-remove "$KNOWN_HOSTS" +} + +# process uids for the authorized_keys file +process_uids_authorized_keys() { + local userID + local ok + local keyid + + # create a lockfile on authorized_keys + lockfile-create "$AUTHORIZED_KEYS" + + for userID ; do + log "processing user ID: $userID" + + process_user_id "$userID" | \ + while read -r ok keyid ; do + sshKey=$(gpg2ssh "$keyid") + # remove the old host key line + remove_line "$AUTHORIZED_KEYS" "$sshKey" + # if key OK, add new host line + if [ "$ok" -eq '0' ] ; then + ssh2authorized_keys "$userID" "$sshKey" >> "$AUTHORIZED_KEYS" + fi + done + # touch the lockfile, for good measure. + lockfile-touch --oneshot "$AUTHORIZED_KEYS" + done + + # remove the lockfile + lockfile-remove "$AUTHORIZED_KEYS" +} + +# process known_hosts file +# go through line-by-line, extract each host, and process with the +# host processing function +process_known_hosts() { + local hosts + local host + + # take all the hosts from the known_hosts file (first field), + # grep out all the hashed hosts (lines starting with '|')... + cat "$KNOWN_HOSTS" | meat | \ + cut -d ' ' -f 1 | grep -v '^|.*$' | \ + while IFS=, read -r -a hosts ; do + process_hosts_known_hosts ${hosts[@]} + done +} + +# process an authorized_user_ids file for authorized_keys +process_authorized_user_ids() { + local userid + + authorizedUserIDs="$1" + + cat "$authorizedUserIDs" | meat | \ + while read -r userid ; do + process_uids_authorized_keys "$userid" + done +} + +# EXPERIMENTAL (unused) process userids found in authorized_keys file +# go through line-by-line, extract monkeysphere userids from comment +# fields, and process each userid +# NOT WORKING +process_authorized_keys() { + local authorizedKeys + local userID + + authorizedKeys="$1" + + # take all the monkeysphere userids from the authorized_keys file + # comment field (third field) that starts with "MonkeySphere uid:" + # FIXME: needs to handle authorized_keys options (field 0) + cat "$authorizedKeys" | meat | \ + while read -r options keytype key comment ; do + # if the comment field is empty, assume the third field was + # the comment + if [ -z "$comment" ] ; then + comment="$key" + fi + + if echo "$comment" | egrep -v -q '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}' ; then + continue + fi + userID=$(echo "$comment" | awk "{ print $2 }") + if [ -z "$userID" ] ; then + continue + fi + + # process the userid + log "processing userid: '$userID'" + process_user_id "$userID" > /dev/null + done +} + +################################################## +### GPG HELPER FUNCTIONS + +# retrieve key from web of trust, and set owner trust to "full" +# if key is found. +trust_key() { + # get the key from the key server + if ! gpg --keyserver "$KEYSERVER" --recv-key "$keyID" ; then + failure "Could not retrieve key '$keyID'." + fi + + # get key fingerprint + fingerprint=$(get_key_fingerprint "$keyID") + + # attach a "non-exportable" signature to the key + # this is required for the key to have any validity at all + # the 'y's on stdin indicates "yes, i really want to sign" + echo -e 'y\ny' | gpg --lsign-key --command-fd 0 "$fingerprint" + + # import "full" trust for fingerprint into gpg + echo ${fingerprint}:5: | gpg --import-ownertrust + if [ $? = 0 ] ; then + log "Owner trust updated." + else + failure "There was a problem changing owner trust." + fi +} + +# publish server key to keyserver +publish_server_key() { + read -p "really publish key to $KEYSERVER? [y|N]: " OK; OK=${OK:=N} + if [ ${OK/y/Y} != 'Y' ] ; then + failure "aborting." + fi + + # publish host key + # FIXME: need to figure out better way to identify host key + # dummy command so as not to publish fakes keys during testing + # eventually: + #gpg --keyserver "$KEYSERVER" --send-keys $(hostname -f) + failure "NOT PUBLISHED (to avoid permanent publication errors during monkeysphere development). +To publish manually, do: gpg --keyserver $KEYSERVER --send-keys $(hostname -f)" +} diff --git a/src/keytrans/Makefile b/src/keytrans/Makefile new file mode 100644 index 0000000..79602ef --- /dev/null +++ b/src/keytrans/Makefile @@ -0,0 +1,12 @@ +all: openpgp2ssh + +openpgp2ssh: openpgp2ssh.c gnutls-helpers.o + gcc -g -Wall --pedantic -o openpgp2ssh openpgp2ssh.c `libgnutls-config --libs --cflags` gnutls-helpers.o + +%.o: %.c + gcc -g -Wall --pedantic -o $@ -c $< + +clean: + rm -f openpgp2ssh *.o + +.PHONY: clean all diff --git a/src/keytrans/gnutls-helpers.c b/src/keytrans/gnutls-helpers.c new file mode 100644 index 0000000..5b4c46a --- /dev/null +++ b/src/keytrans/gnutls-helpers.c @@ -0,0 +1,446 @@ +/* Author: Daniel Kahn Gillmor */ +/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */ +/* License: GPL v3 or later */ + +#include "gnutls-helpers.h" +/* for htonl() */ +#include + +/* for setlocale() */ +#include + +/* for isalnum() */ +#include + +/* for exit() */ +#include + +#include + +/* higher levels allow more frivolous error messages through. + this is set with the MONKEYSPHERE_DEBUG variable */ +static int loglevel = 0; + +void err(int level, const char* fmt, ...) { + va_list ap; + if (level > loglevel) + return; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fflush(stderr); +} + +void logfunc(int level, const char* string) { + fprintf(stderr, "GnuTLS Logging (%d): %s\n", level, string); +} + +void init_keyid(gnutls_openpgp_keyid_t keyid) { + memset(keyid, 'x', sizeof(gnutls_openpgp_keyid_t)); +} + + + +void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid) +{ + assert(sizeof(out) >= 2*sizeof(keyid)); + hex_print_data((char*)out, (const char*)keyid, sizeof(keyid)); +} + +/* you must have twice as many bytes in the out buffer as in the in buffer */ +void hex_print_data(char* out, const char* in, size_t incount) +{ + static const char hex[16] = "0123456789ABCDEF"; + unsigned int inix = 0, outix = 0; + + while (inix < incount) { + out[outix] = hex[(in[inix] >> 4) & 0x0f]; + out[outix + 1] = hex[in[inix] & 0x0f]; + inix++; + outix += 2; + } +} + +unsigned char hex2bin(unsigned char x) { + if ((x >= '0') && (x <= '9')) + return x - '0'; + if ((x >= 'A') && (x <= 'F')) + return 10 + x - 'A'; + if ((x >= 'a') && (x <= 'f')) + return 10 + x - 'a'; + return 0xff; +} + +void collapse_printable_keyid(gnutls_openpgp_keyid_t out, printable_keyid in) { + unsigned int pkix = 0, outkix = 0; + + while (pkix < sizeof(printable_keyid)) { + unsigned hi = hex2bin(in[pkix]); + unsigned lo = hex2bin(in[pkix + 1]); + if (hi == 0xff) { + err(0, "character '%c' is not a hex char\n", in[pkix]); + exit(1); + } + if (lo == 0xff) { + err(0, "character '%c' is not a hex char\n", in[pkix + 1]); + exit(1); + } + out[outkix] = lo | (hi << 4); + + pkix += 2; + outkix++; + } +} + +int convert_string_to_keyid(gnutls_openpgp_keyid_t out, const char* str) { + printable_keyid p; + int ret; + + ret = convert_string_to_printable_keyid(p, str); + if (ret == 0) + collapse_printable_keyid(out, p); + return ret; +} +int convert_string_to_printable_keyid(printable_keyid pkeyid, const char* str) { + int arglen, x; + arglen = 0; + x = 0; + while ((arglen <= sizeof(printable_keyid)) && + (str[x] != '\0')) { + if (isxdigit(str[x])) { + if (arglen == sizeof(printable_keyid)) { + err(0, "There are more than %d hex digits in the keyid '%s'\n", sizeof(printable_keyid), str); + return 1; + } + pkeyid[arglen] = str[x]; + arglen++; + } + x++; + } + + if (arglen != sizeof(printable_keyid)) { + err(0, "Keyid '%s' is not %d hex digits in length\n", str, sizeof(printable_keyid)); + return 1; + } + return 0; +} + + + +int init_gnutls() { + const char* version = NULL; + const char* debug_string = NULL; + int ret; + + if (debug_string = getenv("MONKEYSPHERE_DEBUG"), debug_string) { + loglevel = atoi(debug_string); + } + + if (ret = gnutls_global_init(), ret) { + err(0, "Failed to do gnutls_global_init() (error: %d)\n", ret); + return 1; + } + + version = gnutls_check_version(NULL); + + if (version) + err(1, "gnutls version: %s\n", version); + else { + err(0, "no gnutls version found!\n"); + return 1; + } + + gnutls_global_set_log_function(logfunc); + + gnutls_global_set_log_level(loglevel); + err(1, "set log level to %d\n", loglevel); + + return 0; +} + +void init_datum(gnutls_datum_t* d) { + d->data = NULL; + d->size = 0; +} +void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src) { + dest->data = gnutls_realloc(dest->data, src->size); + dest->size = src->size; + memcpy(dest->data, src->data, src->size); +} +int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b) { + if (a->size > b->size) { + err(0,"a is larger\n"); + return 1; + } + if (a->size < b->size) { + err(0,"b is larger\n"); + return -1; + } + return memcmp(a->data, b->data, a->size); +} +void free_datum(gnutls_datum_t* d) { + gnutls_free(d->data); + d->data = NULL; + d->size = 0; +} + +/* read the passed-in string, store in a single datum */ +int set_datum_string(gnutls_datum_t* d, const char* s) { + unsigned int x = strlen(s)+1; + unsigned char* c = NULL; + + c = gnutls_realloc(d->data, x); + if (NULL == c) + return -1; + d->data = c; + d->size = x; + memcpy(d->data, s, x); + return 0; +} + +/* read the passed-in file descriptor until EOF, store in a single + datum */ +int set_datum_fd(gnutls_datum_t* d, int fd) { + unsigned int bufsize = 1024; + unsigned int len = 0; + + FILE* f = fdopen(fd, "r"); + if (bufsize > d->size) { + bufsize = 1024; + d->data = gnutls_realloc(d->data, bufsize); + if (d->data == NULL) { + err(0,"out of memory!\n"); + return -1; + } + d->size = bufsize; + } else { + bufsize = d->size; + } + f = fdopen(fd, "r"); + if (NULL == f) { + err(0,"could not fdopen FD %d\n", fd); + } + clearerr(f); + while (!feof(f) && !ferror(f)) { + if (len == bufsize) { + /* allocate more space by doubling: */ + bufsize *= 2; + d->data = gnutls_realloc(d->data, bufsize); + if (d->data == NULL) { + err(0,"out of memory!\n"); + return -1; + }; + d->size = bufsize; + } + len += fread(d->data + len, 1, bufsize - len, f); + /* err(0,"read %d bytes\n", len); */ + } + if (ferror(f)) { + err(0,"Error reading from fd %d (error: %d) (error: %d '%s')\n", fd, ferror(f), errno, strerror(errno)); + return -1; + } + + /* touch up buffer size to match reality: */ + d->data = gnutls_realloc(d->data, len); + d->size = len; + return 0; +} + +/* read the file indicated (by name) in the fname parameter. store + its entire contents in a single datum. */ +int set_datum_file(gnutls_datum_t* d, const char* fname) { + struct stat sbuf; + unsigned char* c = NULL; + FILE* file = NULL; + size_t x = 0; + + if (0 != stat(fname, &sbuf)) { + err(0,"failed to stat '%s'\n", fname); + return -1; + } + + c = gnutls_realloc(d->data, sbuf.st_size); + if (NULL == c) { + err(0,"failed to allocate %d bytes for '%s'\n", sbuf.st_size, fname); + return -1; + } + + d->data = c; + d->size = sbuf.st_size; + file = fopen(fname, "r"); + if (NULL == file) { + err(0,"failed to open '%s' for reading\n", fname); + return -1; + } + + x = fread(d->data, d->size, 1, file); + if (x != 1) { + err(0,"tried to read %d bytes, read %d instead from '%s'\n", d->size, x, fname); + fclose(file); + return -1; + } + fclose(file); + return 0; +} + +int write_datum_fd(int fd, const gnutls_datum_t* d) { + if (d->size != write(fd, d->data, d->size)) { + err(0,"failed to write body of datum.\n"); + return -1; + } + return 0; +} + + +int write_datum_fd_with_length(int fd, const gnutls_datum_t* d) { + uint32_t len; + int looks_negative = (d->data[0] & 0x80); + unsigned char zero = 0; + + /* if the first bit is 1, then the datum will appear negative in the + MPI encoding style used by OpenSSH. In that case, we'll increase + the length by one, and dump out one more byte */ + + if (looks_negative) { + len = htonl(d->size + 1); + } else { + len = htonl(d->size); + } + if (write(fd, &len, sizeof(len)) != sizeof(len)) { + err(0,"failed to write size of datum.\n"); + return -2; + } + if (looks_negative) { + if (write(fd, &zero, 1) != 1) { + err(0,"failed to write padding byte for MPI.\n"); + return -2; + } + } + return write_datum_fd(fd, d); +} + +int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num) { + unsigned int i; + int ret; + + for (i = 0; i < num; i++) + if (ret = write_datum_fd_with_length(fd, d[i]), ret != 0) + return ret; + + return 0; +} + + +int datum_from_string(gnutls_datum_t* d, const char* str) { + d->size = strlen(str); + d->data = gnutls_realloc(d->data, d->size); + if (d->data == 0) + return ENOMEM; + memcpy(d->data, str, d->size); + return 0; +} + + +int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]) { + int p[2]; + int ret; + + if (pid == NULL) { + err(0,"bad pointer passed to create_writing_pipe()\n"); + return -1; + } + + if (ret = pipe(p), ret == -1) { + err(0,"failed to create a pipe (error: %d \"%s\")\n", errno, strerror(errno)); + return -1; + } + + *pid = fork(); + if (*pid == -1) { + err(0,"Failed to fork (error: %d \"%s\")\n", errno, strerror(errno)); + return -1; + } + if (*pid == 0) { /* this is the child */ + close(p[1]); /* close unused write end */ + + if (0 != dup2(p[0], 0)) { /* map the reading end into stdin */ + err(0,"Failed to transfer reading file descriptor to stdin (error: %d \"%s\")\n", errno, strerror(errno)); + exit(1); + } + execv(path, argv); + err(0,"exec %s failed (error: %d \"%s\")\n", path, errno, strerror(errno)); + /* close the open file descriptors */ + close(p[0]); + close(0); + + exit(1); + } else { /* this is the parent */ + close(p[0]); /* close unused read end */ + return p[1]; + } +} + +int validate_ssh_host_userid(const char* userid) { + char* oldlocale = setlocale(LC_ALL, "C"); + + /* choke if userid does not match the expected format + ("ssh://fully.qualified.domain.name") */ + if (strncmp("ssh://", userid, strlen("ssh://")) != 0) { + err(0,"The user ID should start with ssh:// for a host key\n"); + goto fail; + } + /* so that isalnum will work properly */ + userid += strlen("ssh://"); + while (0 != (*userid)) { + if (!isalnum(*userid)) { + err(0,"label did not start with a letter or a digit! (%s)\n", userid); + goto fail; + } + userid++; + while (isalnum(*userid) || ('-' == (*userid))) + userid++; + if (('.' == (*userid)) || (0 == (*userid))) { /* clean end of label: + check last char + isalnum */ + if (!isalnum(*(userid - 1))) { + err(0,"label did not end with a letter or a digit!\n"); + goto fail; + } + if ('.' == (*userid)) /* advance to the start of the next label */ + userid++; + } else { + err(0,"invalid character in domain name: %c\n", *userid); + goto fail; + } + } + /* ensure that the last character is valid: */ + if (!isalnum(*(userid - 1))) { + err(0,"hostname did not end with a letter or a digit!\n"); + goto fail; + } + /* FIXME: fqdn's can be unicode now, thanks to RFC 3490 -- how do we + make sure that we've got an OK string? */ + + return 0; + + fail: + setlocale(LC_ALL, oldlocale); + return 1; +} + +/* http://tools.ietf.org/html/rfc4880#section-5.5.2 */ +size_t get_openpgp_mpi_size(gnutls_datum_t* d) { + return 2 + d->size; +} + +int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d) { + uint16_t x; + + x = d->size * 8; + x = htons(x); + + write(fd, &x, sizeof(x)); + write(fd, d->data, d->size); + + return 0; +} diff --git a/src/keytrans/gnutls-helpers.h b/src/keytrans/gnutls-helpers.h new file mode 100644 index 0000000..f196456 --- /dev/null +++ b/src/keytrans/gnutls-helpers.h @@ -0,0 +1,78 @@ +/* Author: Daniel Kahn Gillmor */ +/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */ +/* License: GPL v3 or later */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Functions to help dealing with GnuTLS for monkeysphere key + translation projects: */ + +/* set everything up, including logging levels. Return 0 on + success */ +int init_gnutls(); + +/* logging and output functions: */ + +void err(int level, const char* fmt, ...); +void logfunc(int level, const char* string); + +/* basic datum manipulations: */ + +void init_datum(gnutls_datum_t* d); +void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src); +int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b); +void free_datum(gnutls_datum_t* d); +int write_datum_fd(int fd, const gnutls_datum_t* d); +int write_datum_fd_with_length(int fd, const gnutls_datum_t* d); +int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num); + +/* set up a datum from a null-terminated string */ +int datum_from_string(gnutls_datum_t* d, const char* str); + +/* keyid manipulations: */ +typedef unsigned char printable_keyid[16]; + +void init_keyid(gnutls_openpgp_keyid_t keyid); +void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid); +void collapse_printable_keyid(gnutls_openpgp_keyid_t out, printable_keyid in); +int convert_string_to_keyid(gnutls_openpgp_keyid_t out, const char* str); +int convert_string_to_printable_keyid(printable_keyid out, const char* str); + +/* you must have twice as many bytes in the out buffer as in the in buffer */ +void hex_print_data(char* out, const char* in, size_t incount); + +/* functions to get data into datum objects: */ + +/* read the passed-in string, store in a single datum */ +int set_datum_string(gnutls_datum_t* d, const char* s); + +/* read the passed-in file descriptor until EOF, store in a single + datum */ +int set_datum_fd(gnutls_datum_t* d, int fd); + +/* read the file indicated (by name) in the fname parameter. store + its entire contents in a single datum. */ +int set_datum_file(gnutls_datum_t* d, const char* fname); + +/* set up file descriptor pipe for writing (child process pid gets + stored in pid, fd is returned)*/ +int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]); + +/* return 0 if userid matches the monkeysphere spec for ssh host user IDs */ +int validate_ssh_host_userid(const char* userid); + +/* how many bytes will it take to write out this datum in OpenPGP MPI form? */ +size_t get_openpgp_mpi_size(gnutls_datum_t* d); + +/* write the MPI stored in gnutls_datum_t to file descriptor fd: */ +int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d); diff --git a/src/keytrans/openpgp2ssh.c b/src/keytrans/openpgp2ssh.c new file mode 100644 index 0000000..92bdc19 --- /dev/null +++ b/src/keytrans/openpgp2ssh.c @@ -0,0 +1,437 @@ +#include "gnutls-helpers.h" + +#include +#include + +/* for waitpid() */ +#include +#include + +/* + Author: Daniel Kahn Gillmor + Date: 2008-06-12 13:47:41-0400 + License: GPL v3 or later + + monkeysphere key translator: execute this with an OpenPGP key on + stdin, (please indicate the specific keyid that you want as the + first argument if there are subkeys). At the moment, only public + keys and passphraseless secret keys work. + + For secret keys, it will spit out a PEM-encoded version of the key + on stdout, which can be fed into ssh-add like this: + + gpg --export-secret-keys $KEYID | openpgp2ssh $KEYID | ssh-add -c /dev/stdin + + For public keys, it will spit out a single line of text that can + (with some massaging) be used in an openssh known_hosts or + authorized_keys file. For example: + + echo server.example.org $(gpg --export $KEYID | openpgp2ssh $KEYID) >> ~/.ssh/known_hosts + + Requirements: I've only built this so far with GnuTLS v2.3.x. + GnuTLS 2.2.x does not contain the appropriate functionality. + + */ + + +/* FIXME: keyid should be const as well */ +int convert_private_pgp_to_x509(gnutls_x509_privkey_t* output, const gnutls_openpgp_privkey_t* pgp_privkey, gnutls_openpgp_keyid_t* keyid) { + gnutls_datum_t m, e, d, p, q, u, g, y, x; + gnutls_pk_algorithm_t pgp_algo; + unsigned int pgp_bits; + int ret; + gnutls_openpgp_keyid_t curkeyid; + int subkeyidx; + int subkeycount; + int found = 0; + + init_datum(&m); + init_datum(&e); + init_datum(&d); + init_datum(&p); + init_datum(&q); + init_datum(&u); + init_datum(&g); + init_datum(&y); + init_datum(&x); + + subkeycount = gnutls_openpgp_privkey_get_subkey_count(*pgp_privkey); + if (subkeycount < 0) { + err(0,"Could not determine subkey count (got value %d)\n", subkeycount); + return 1; + } + + if ((keyid == NULL) && + (subkeycount > 0)) { + err(0,"No keyid passed in, but there were %d keys to choose from\n", subkeycount + 1); + return 1; + } + + if (keyid != NULL) { + ret = gnutls_openpgp_privkey_get_key_id(*pgp_privkey, curkeyid); + if (ret) { + err(0,"Could not get keyid (error: %d)\n", ret); + return 1; + } + } + if ((keyid == NULL) || (memcmp(*keyid, curkeyid, sizeof(gnutls_openpgp_keyid_t)) == 0)) { + /* we want to export the primary key: */ + err(0,"exporting primary key\n"); + + /* FIXME: this is almost identical to the block below for subkeys. + This clumsiness seems inherent in the gnutls OpenPGP API, + though. ugh. */ + pgp_algo = gnutls_openpgp_privkey_get_pk_algorithm(*pgp_privkey, &pgp_bits); + if (pgp_algo < 0) { + err(0, "failed to get OpenPGP key algorithm (error: %d)\n", pgp_algo); + return 1; + } + if (pgp_algo == GNUTLS_PK_RSA) { + err(0,"OpenPGP RSA Key, with %d bits\n", pgp_bits); + ret = gnutls_openpgp_privkey_export_rsa_raw(*pgp_privkey, &m, &e, &d, &p, &q, &u); + if (GNUTLS_E_SUCCESS != ret) { + err(0, "failed to export RSA key parameters (error: %d)\n", ret); + return 1; + } + + } else if (pgp_algo == GNUTLS_PK_DSA) { + err(0,"OpenPGP DSA Key, with %d bits\n", pgp_bits); + ret = gnutls_openpgp_privkey_export_dsa_raw(*pgp_privkey, &p, &q, &g, &y, &x); + if (GNUTLS_E_SUCCESS != ret) { + err(0,"failed to export DSA key parameters (error: %d)\n", ret); + return 1; + } + } + found = 1; + } else { + /* lets trawl through the subkeys until we find the one we want: */ + for (subkeyidx = 0; (subkeyidx < subkeycount) && !found; subkeyidx++) { + ret = gnutls_openpgp_privkey_get_subkey_id(*pgp_privkey, subkeyidx, curkeyid); + if (ret) { + err(0,"Could not get keyid of subkey with index %d (error: %d)\n", subkeyidx, ret); + return 1; + } + if (memcmp(*keyid, curkeyid, sizeof(gnutls_openpgp_keyid_t)) == 0) { + err(0,"exporting subkey index %d\n", subkeyidx); + + /* FIXME: this is almost identical to the block above for the + primary key. */ + pgp_algo = gnutls_openpgp_privkey_get_subkey_pk_algorithm(*pgp_privkey, subkeyidx, &pgp_bits); + if (pgp_algo < 0) { + err(0,"failed to get the algorithm of the OpenPGP public key (error: %d)\n", pgp_algo); + return pgp_algo; + } else if (pgp_algo == GNUTLS_PK_RSA) { + err(0,"OpenPGP RSA key, with %d bits\n", pgp_bits); + ret = gnutls_openpgp_privkey_export_subkey_rsa_raw(*pgp_privkey, subkeyidx, &m, &e, &d, &p, &q, &u); + if (GNUTLS_E_SUCCESS != ret) { + err(0,"failed to export RSA key parameters (error: %d)\n", ret); + return 1; + } + } else if (pgp_algo == GNUTLS_PK_DSA) { + err(0,"OpenPGP DSA Key, with %d bits\n", pgp_bits); + ret = gnutls_openpgp_privkey_export_subkey_dsa_raw(*pgp_privkey, subkeyidx, &p, &q, &g, &y, &x); + if (GNUTLS_E_SUCCESS != ret) { + err(0,"failed to export DSA key parameters (error: %d)\n", ret); + return 1; + } + } + found = 1; + } + } + } + + if (!found) { + err(0,"Could not find key in input\n"); + return 1; + } + + if (pgp_algo == GNUTLS_PK_RSA) { + ret = gnutls_x509_privkey_import_rsa_raw (*output, &m, &e, &d, &p, &q, &u); + if (GNUTLS_E_SUCCESS != ret) { + err(0, "failed to import RSA key parameters (error: %d)\n", ret); + return 1; + } + } else if (pgp_algo == GNUTLS_PK_DSA) { + ret = gnutls_x509_privkey_import_dsa_raw (*output, &p, &q, &g, &y, &x); + if (GNUTLS_E_SUCCESS != ret) { + err(0,"failed to import DSA key parameters (error: %d)\n", ret); + return 1; + } + } else { + err(0,"OpenPGP Key was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", pgp_algo); + return 1; + } + + ret = gnutls_x509_privkey_fix(*output); + if (ret != 0) { + err(0,"failed to fix up the private key in X.509 format (error: %d)\n", ret); + return 1; + } + + return 0; +} + +/* FIXME: keyid should be const also */ +int emit_public_openssh_from_pgp(const gnutls_openpgp_crt_t* pgp_crt, gnutls_openpgp_keyid_t* keyid) { + gnutls_openpgp_keyid_t curkeyid; + int ret; + int subkeyidx; + int subkeycount; + int found = 0; + gnutls_datum_t m, e, p, q, g, y, algolabel; + unsigned int bits; + gnutls_pk_algorithm_t algo; + const gnutls_datum_t* all[5]; + const char* algoname; + int mpicount; + /* output_data must be at least 2 chars longer than the maximum possible + algorithm name: */ + char output_data[20]; + + /* variables for the output conversion: */ + int pipestatus; + int pipefd, child_pid; + char* const b64args[] = {"/usr/bin/base64", "--wrap=0", NULL}; + + init_datum(&m); + init_datum(&e); + init_datum(&p); + init_datum(&q); + init_datum(&g); + init_datum(&algolabel); + + + /* figure out if we've got the right thing: */ + subkeycount = gnutls_openpgp_crt_get_subkey_count(*pgp_crt); + if (subkeycount < 0) { + err(0,"Could not determine subkey count (got value %d)\n", subkeycount); + return 1; + } + + if ((keyid == NULL) && + (subkeycount > 0)) { + err(0,"No keyid passed in, but there were %d keys to choose from\n", subkeycount + 1); + return 1; + } + + if (keyid != NULL) { + ret = gnutls_openpgp_crt_get_key_id(*pgp_crt, curkeyid); + if (ret) { + err(0,"Could not get keyid (error: %d)\n", ret); + return 1; + } + } + if ((keyid == NULL) || (memcmp(*keyid, curkeyid, sizeof(gnutls_openpgp_keyid_t)) == 0)) { + /* we want to export the primary key: */ + err(0,"exporting primary key\n"); + + /* FIXME: this is almost identical to the block below for subkeys. + This clumsiness seems inherent in the gnutls OpenPGP API, + though. ugh. */ + algo = gnutls_openpgp_crt_get_pk_algorithm(*pgp_crt, &bits); + if (algo < 0) { + err(0,"failed to get the algorithm of the OpenPGP public key (error: %d)\n", algo); + return algo; + } else if (algo == GNUTLS_PK_RSA) { + err(0,"OpenPGP RSA certificate, with %d bits\n", bits); + ret = gnutls_openpgp_crt_get_pk_rsa_raw(*pgp_crt, &m, &e); + if (GNUTLS_E_SUCCESS != ret) { + err(0,"failed to export RSA certificate parameters (error: %d)\n", ret); + return 1; + } + } else if (algo == GNUTLS_PK_DSA) { + err(0,"OpenPGP DSA certificate, with %d bits\n", bits); + ret = gnutls_openpgp_crt_get_pk_dsa_raw(*pgp_crt, &p, &q, &g, &y); + if (GNUTLS_E_SUCCESS != ret) { + err(0,"failed to export DSA certificate parameters (error: %d)\n", ret); + return 1; + } + } + found = 1; + + } else { + /* lets trawl through the subkeys until we find the one we want: */ + for (subkeyidx = 0; (subkeyidx < subkeycount) && !found; subkeyidx++) { + ret = gnutls_openpgp_crt_get_subkey_id(*pgp_crt, subkeyidx, curkeyid); + if (ret) { + err(0,"Could not get keyid of subkey with index %d (error: %d)\n", subkeyidx, ret); + return 1; + } + if (memcmp(*keyid, curkeyid, sizeof(gnutls_openpgp_keyid_t)) == 0) { + err(0,"exporting subkey index %d\n", subkeyidx); + + /* FIXME: this is almost identical to the block above for the + primary key. */ + algo = gnutls_openpgp_crt_get_subkey_pk_algorithm(*pgp_crt, subkeyidx, &bits); + if (algo < 0) { + err(0,"failed to get the algorithm of the OpenPGP public key (error: %d)\n", algo); + return algo; + } else if (algo == GNUTLS_PK_RSA) { + err(0,"OpenPGP RSA certificate, with %d bits\n", bits); + ret = gnutls_openpgp_crt_get_subkey_pk_rsa_raw(*pgp_crt, subkeyidx, &m, &e); + if (GNUTLS_E_SUCCESS != ret) { + err(0,"failed to export RSA certificate parameters (error: %d)\n", ret); + return 1; + } + } else if (algo == GNUTLS_PK_DSA) { + err(0,"OpenPGP DSA certificate, with %d bits\n", bits); + ret = gnutls_openpgp_crt_get_subkey_pk_dsa_raw(*pgp_crt, subkeyidx, &p, &q, &g, &y); + if (GNUTLS_E_SUCCESS != ret) { + err(0,"failed to export DSA certificate parameters (error: %d)\n", ret); + return 1; + } + } + found = 1; + + } + } + } + + if (!found) { + err(0,"Could not find key in input\n"); + return 1; + } + + /* if we made it this far, we've got MPIs, and we've got the + algorithm, so we just need to emit the info */ + if (algo == GNUTLS_PK_RSA) { + algoname = "ssh-rsa"; + mpicount = 3; + + all[0] = &algolabel; + all[1] = &e; + all[2] = &m; + } else if (algo == GNUTLS_PK_DSA) { + algoname = "ssh-dss"; + mpicount = 5; + + all[0] = &algolabel; + all[1] = &p; + all[2] = &q; + all[3] = &g; + all[4] = &y; + } else { + err(0,"Key algorithm was neither DSA nor RSA (it was %d). Can't deal. Sorry!\n", algo); + return 1; + } + + if (ret = datum_from_string(&algolabel, algoname), ret) { + err(0,"couldn't label string (error: %d)\n", ret); + return ret; + } + + snprintf(output_data, sizeof(output_data), "%s ", algoname); + + pipefd = create_writing_pipe(&child_pid, b64args[0], b64args); + if (pipefd < 0) { + err(0,"failed to create a writing pipe (returned %d)\n", pipefd); + return pipefd; + } + + write(1, output_data, strlen(output_data)); + + if (0 != write_data_fd_with_length(pipefd, all, mpicount)) { + err(0,"was not able to write out RSA key data\n"); + return 1; + } + close(pipefd); + if (child_pid != waitpid(child_pid, &pipestatus, 0)) { + err(0,"could not wait for child process to return for some reason.\n"); + return 1; + } + if (pipestatus != 0) { + err(0,"base64 pipe died with return code %d\n", pipestatus); + return pipestatus; + } + + write(1, "\n", 1); + + return 0; +} + +int main(int argc, char* argv[]) { + gnutls_datum_t data; + int ret; + gnutls_x509_privkey_t x509_privkey; + gnutls_openpgp_privkey_t pgp_privkey; + gnutls_openpgp_crt_t pgp_crt; + + char output_data[10240]; + size_t ods = sizeof(output_data); + + gnutls_openpgp_keyid_t keyid; + gnutls_openpgp_keyid_t* use_keyid; + + init_gnutls(); + + /* figure out what keyid we should be looking for: */ + use_keyid = NULL; + if (argv[1] != NULL) { + ret = convert_string_to_keyid(keyid, argv[1]); + if (ret != 0) + return ret; + use_keyid = &keyid; + } + + + init_datum(&data); + + /* slurp in the key from stdin */ + if (ret = set_datum_fd(&data, 0), ret) { + err(0,"didn't read file descriptor 0\n"); + return 1; + } + + + if (ret = gnutls_openpgp_privkey_init(&pgp_privkey), ret) { + err(0,"Failed to initialized OpenPGP private key (error: %d)\n", ret); + return 1; + } + /* check whether it's a private key or a public key, by trying them: */ + if ((gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_RAW, NULL, 0) == 0) || + (gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_BASE64, NULL, 0) == 0)) { + /* we're dealing with a private key */ + err(0,"Translating private key\n"); + if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) { + err(0,"Failed to initialize X.509 private key for output (error: %d)\n", ret); + return 1; + } + + ret = convert_private_pgp_to_x509(&x509_privkey, &pgp_privkey, use_keyid); + + gnutls_openpgp_privkey_deinit(pgp_privkey); + if (ret) + return ret; + + ret = gnutls_x509_privkey_export (x509_privkey, + GNUTLS_X509_FMT_PEM, + output_data, + &ods); + if (ret == 0) { + write(1, output_data, ods); + } + gnutls_x509_privkey_deinit(x509_privkey); + + } else { + if (ret = gnutls_openpgp_crt_init(&pgp_crt), ret) { + err(0,"Failed to initialized OpenPGP certificate (error: %d)\n", ret); + return 1; + } + + if ((gnutls_openpgp_crt_import(pgp_crt, &data, GNUTLS_OPENPGP_FMT_RAW) == 0) || + (gnutls_openpgp_crt_import(pgp_crt, &data, GNUTLS_OPENPGP_FMT_BASE64) == 0)) { + /* we're dealing with a public key */ + err(0,"Translating public key\n"); + + ret = emit_public_openssh_from_pgp(&pgp_crt, use_keyid); + + } else { + /* we have no idea what kind of key this is at all anyway! */ + err(0,"Input does not contain any form of OpenPGP key I recognize.\n"); + return 1; + } + } + + gnutls_global_deinit(); + return 0; +} diff --git a/src/monkeysphere b/src/monkeysphere new file mode 100755 index 0000000..9b315e2 --- /dev/null +++ b/src/monkeysphere @@ -0,0 +1,204 @@ +#!/bin/bash + +# monkeysphere: MonkeySphere client tool +# +# The monkeysphere scripts are written by: +# Jameson Rollins +# +# They are Copyright 2008, and are all released under the GPL, version 3 +# or later. + +######################################################################## +PGRM=$(basename $0) + +SHAREDIR=${SHAREDIR:-"/usr/share/monkeysphere"} +export SHAREDIR +. "${SHAREDIR}/common" + +GLOBAL_CONFIG=${GLOBAL_CONFIG:-"${ETC}/monkeysphere.conf"} +[ -r "$GLOBAL_CONFIG" ] && . "$GLOBAL_CONFIG" + +# date in UTF format if needed +DATE=$(date -u '+%FT%T') + +# unset some environment variables that could screw things up +GREP_OPTIONS= + +######################################################################## +# FUNCTIONS +######################################################################## + +usage() { +cat < [args] +MonkeySphere client tool. + +subcommands: + update-known_hosts (k) [HOST]... update known_hosts file + update-authorized_keys (a) update authorized_keys file + gen-subkey (g) KEYID generate an 'a' capable subkey + help (h,?) this help + +EOF +} + +# generate a subkey with the 'a' usage flags set +# FIXME: this needs some tweaking to clean it up +gen_subkey(){ + local keyID + local gpgOut + local userID + + keyID="$1" + + gpgOut=$(gpg --quiet --fixed-list-mode --list-keys --with-colons \ + "$keyID" 2> /dev/null) + + # fail if there only "tru" lines are output from gpg, which + # indicates the key was not found. + if [ -z "$(echo "$gpgOut" | grep -v '^tru:')" ] ; then + failure "Key ID '$keyID' not found." + fi + + # fail if multiple pub lines are returned, which means the id given + # is not unique + if [ $(echo "$gpgOut" | grep '^pub:' | wc -l) -gt '1' ] ; then + failure "Key ID '$keyID' is not unique." + fi + + # prompt if an authentication subkey already exists + if echo "$gpgOut" | egrep "^(pub|sub):" | cut -d: -f 12 | grep -q a ; then + echo "An authentication subkey already exists for key '$keyID'." + read -p "Are you sure you would like to generate another one? [y|N]: " OK; OK=${OK:N} + if [ "${OK/y/Y}" != 'Y' ] ; then + failure "aborting." + fi + fi + + # set subkey defaults + SUBKEY_TYPE=${SUBKEY_TYPE:-"RSA"} + SUBKEY_LENGTH=${SUBKEY_LENGTH:-} + SUBKEY_USAGE=${SUBKEY_USAGE:-"auth"} + SUBKEY_EXPIRE=${SUBKEY_EXPIRE:-"0"} + cat < = key expires in n days + w = key expires in n weeks + m = key expires in n months + y = key expires in n years +EOF + read -p "Key is valid for? ($SUBKEY_EXPIRE) " SUBKEY_EXPIRE; SUBKEY_EXPIRE=${SUBKEY_EXPIRE:-"0"} + + # generate the list of commands that will be passed to edit-key + editCommands=$(cat < +# +# They are Copyright 2008, and are all released under the GPL, version 3 +# or later. + +######################################################################## +PGRM=$(basename $0) + +SHAREDIR=${SHAREDIR:-"/usr/share/monkeysphere"} +export SHAREDIR +. "${SHAREDIR}/common" + +# date in UTF format if needed +DATE=$(date -u '+%FT%T') + +# unset some environment variables that could screw things up +GREP_OPTIONS= + +######################################################################## +# FUNCTIONS +######################################################################## + +usage() { +cat < [args] +MonkeySphere server admin tool. + +subcommands: + update-users (s) [USER]... update users authorized_keys files + gen-key (g) [HOSTNAME] generate gpg key for the server + show-fingerprint (f) show server's host key fingerprint + publish-key (p) publish server key to keyserver + trust-keys (t) KEYID... mark keyids as trusted + help (h,?) this help + +EOF +} + +# generate server gpg key +gen_key() { + local hostName + + hostName=${1:-$(hostname --fqdn)} + service=${SERVICE:-"ssh"} + userID="${service}://${hostName}" + + if gpg --list-key ="$userID" > /dev/null 2>&1 ; then + failure "Key for '$userID' already exists" + fi + + # set key defaults + KEY_TYPE=${KEY_TYPE:-"RSA"} + KEY_LENGTH=${KEY_LENGTH:-"2048"} + KEY_USAGE=${KEY_USAGE:-"auth"} + KEY_EXPIRE=${KEY_EXPIRE:-"0"} + cat < = key expires in n days + w = key expires in n weeks + m = key expires in n months + y = key expires in n years +EOF + read -p "Key is valid for? ($KEY_EXPIRE) " KEY_EXPIRE; KEY_EXPIRE=${KEY_EXPIRE:-"0"} + + # set key parameters + keyParameters=$(cat < "${MS_HOME}/ssh_host_rsa_key") + log "Private SSH host key output to file: ${MS_HOME}/ssh_host_rsa_key" +} + +# gpg output key fingerprint +fingerprint_server_key() { + local ID + + if [ -z "$1" ] ; then + ID="$1" + else + ID="=ssh://$(hostname --fqdn)" + fi + + gpg --fingerprint --list-secret-keys "$ID" +} + +######################################################################## +# MAIN +######################################################################## + +COMMAND="$1" +[ "$COMMAND" ] || failure "Type '$PGRM help' for usage." +shift + +# set ms home directory +MS_HOME=${MS_HOME:-"$ETC"} + +# load configuration file +MS_CONF=${MS_CONF:-"$MS_HOME"/monkeysphere-server.conf} +[ -e "$MS_CONF" ] && . "$MS_CONF" + +# set empty config variable with defaults +GNUPGHOME=${GNUPGHOME:-"${MS_HOME}/gnupg"} +KEYSERVER=${KEYSERVER:-"subkeys.pgp.net"} +CHECK_KEYSERVER=${CHECK_KEYSERVER:="true"} +REQUIRED_USER_KEY_CAPABILITY=${REQUIRED_USER_KEY_CAPABILITY:-"a"} +AUTHORIZED_USER_IDS=${AUTHORIZED_USER_IDS:-"%h/.config/monkeysphere/authorized_user_ids"} +USER_CONTROLLED_AUTHORIZED_KEYS=${USER_CONTROLLED_AUTHORIZED_KEYS:-"%h/.ssh/authorized_keys"} + +export GNUPGHOME + +# make sure the monkeysphere home directory exists +mkdir -p "${MS_HOME}/authorized_user_ids" +# make sure gpg home exists with proper permissions +mkdir -p -m 0700 "$GNUPGHOME" +# make sure the authorized_keys directory exists +mkdir -p "${CACHE}/authorized_keys" + +case $COMMAND in + 'update-users'|'update-user'|'s') + if [ "$1" ] ; then + # get users from command line + unames="$@" + else + # or just look at all users if none specified + unames=$(getent passwd | cut -d: -f1) + fi + + # loop over users + for uname in $unames ; do + MODE="authorized_keys" + + # check all specified users exist + if ! getent passwd "$uname" >/dev/null ; then + error "----- unknown user '$uname' -----" + continue + fi + + # set authorized_user_ids variable, + # translate ssh-style path variables + authorizedUserIDs=$(translate_ssh_variables "$uname" "$AUTHORIZED_USER_IDS") + + # skip user if authorized_user_ids file does not exist + if [ ! -f "$authorizedUserIDs" ] ; then + continue + fi + + log "----- user: $uname -----" + + # temporary authorized_keys file + AUTHORIZED_KEYS=$(mktemp) + + # skip if the user's authorized_user_ids file is empty + if [ ! -s "$authorizedUserIDs" ] ; then + log "authorized_user_ids file '$authorizedUserIDs' is empty." + continue + fi + + # process authorized_user_ids file + log "processing authorized_user_ids file..." + process_authorized_user_ids "$authorizedUserIDs" + + # add user-controlled authorized_keys file path if specified + if [ "$USER_CONTROLLED_AUTHORIZED_KEYS" != '-' ] ; then + userAuthorizedKeys=$(translate_ssh_variables "$uname" "$USER_CONTROLLED_AUTHORIZED_KEYS") + if [ -f "$userAuthorizedKeys" ] ; then + log -n "adding user's authorized_keys file... " + cat "$userAuthorizedKeys" >> "$AUTHORIZED_KEYS" + loge "done." + fi + fi + + # move the temp authorized_keys file into place + mv -f "$AUTHORIZED_KEYS" "${CACHE}/authorized_keys/${uname}" + + log "authorized_keys file updated." + done + ;; + + 'gen-key'|'g') + gen_key "$1" + ;; + + 'show-fingerprint'|'f') + fingerprint_server_key "$@" + ;; + + 'publish-key'|'p') + publish_server_key + ;; + + 'trust-keys'|'trust-key'|'t') + if [ -z "$1" ] ; then + failure "You must specify at least one key to trust." + fi + + # process key IDs + for keyID ; do + trust_key "$keyID" + done + ;; + + 'help'|'h'|'?') + usage + ;; + + *) + failure "Unknown command: '$COMMAND' +Type '$PGRM help' for usage." + ;; +esac + +exit "$ERR" diff --git a/src/monkeysphere-ssh-proxycommand b/src/monkeysphere-ssh-proxycommand new file mode 100755 index 0000000..f4d4b0d --- /dev/null +++ b/src/monkeysphere-ssh-proxycommand @@ -0,0 +1,80 @@ +#!/bin/sh -e + +# monkeysphere-ssh-proxycommand: MonkeySphere ssh ProxyCommand hook +# +# The monkeysphere scripts are written by: +# Jameson Rollins +# +# They are Copyright 2008, and are all released under the GPL, version 3 +# or later. + +# This is meant to be run as an ssh ProxyCommand to initiate a +# monkeysphere known_hosts update before an ssh connection to host is +# established. Can be added to ~/.ssh/config as follows: +# ProxyCommand monkeysphere-ssh-proxycommand %h %p + +usage() { +cat <&2 +usage: ssh -o ProxyCommand="$(basename $0) %h %p" ... +EOF +} + +log() { + echo "$@" >&2 +} + +if [ "$1" = '--no-connect' ] ; then + NO_CONNECT='true' + shift 1 +fi + +HOST="$1" +PORT="$2" + +if [ -z "$HOST" ] ; then + log "host must be specified." + usage + exit 1 +fi +if [ -z "$PORT" ] ; then + log "port must be specified." + usage + exit 1 +fi + +# set the host URI +URI="ssh://${HOST}" +if [ "$PORT" != '22' ] ; then + URI="${URI}:$PORT" +fi + +# if the host is in the gpg keyring... +if gpg --list-key ="${URI}" 2>&1 >/dev/null ; then + # do not check the keyserver + CHECK_KEYSERVER="false" +# if the host is NOT in the keyring... +else + # if the host key is found in the known_hosts file... + # FIXME: this only works for default known_hosts location + hostKey=$(ssh-keygen -F "$HOST") + if [ "$hostKey" ] ; then + # if the check keyserver variable is NOT set to true... + if [ "$CHECK_KEYSERVER" != 'true' ] ; then + # schedule a keyserver check for host at a later time + echo "monkeysphere update-known_hosts $HOST" | at noon + fi + # if the host key is not found in the known_hosts file... + else + # check the keyserver + CHECK_KEYSERVER="true" + fi +fi +export CHECK_KEYSERVER + +# update the known_hosts file for the host +monkeysphere update-known_hosts "$HOST" + +# exec a netcat passthrough to host for the ssh connection +if [ -z "$NO_CONNECT" ] ; then + exec nc "$HOST" "$PORT" +fi diff --git a/src/seckey2sshagent b/src/seckey2sshagent new file mode 100755 index 0000000..1266db5 --- /dev/null +++ b/src/seckey2sshagent @@ -0,0 +1,52 @@ +#!/bin/sh + +# seckey2sshagent: this is a hack of a script to cope with the fact +# that openpgp2ssh currently cannot support encrypted secret keys. + +# the basic operating principal is: + +# export the secret key in encrypted format to a new keyring + +# remove the passphrase in that keyring + +# use that keyring with openpgp2ssh + +# Authors: Daniel Kahn Gillmor , +# Jameson Rollins + + +cleanup() { + echo -n "removing temp gpg home... " 1>&2 + rm -rf $FOO + echo "done." 1>&2 +} + +trap cleanup EXIT + +#GPGID="$1" +GPGID=$(echo "$1" | cut -c 25-) + +FOO=$(mktemp -d) + +gpg --export-secret-key $GPGID | GNUPGHOME="$FOO" gpg --import + +# idea to script the password stuff. not working. +# read -s -p "enter gpg password: " PASSWD; echo +# cmd=$(cat <