summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMicah Anderson <micah@riseup.net>2008-06-18 23:35:20 -0400
committerMicah Anderson <micah@riseup.net>2008-06-18 23:35:20 -0400
commit308aa104f66a40f2426c13b96f48631937502f6b (patch)
tree55e1b1427ba23750b0c239e5d43fc3ffa49b0293
parenta9a56853a27e1dbce3c48af327b0adff0e4c38e0 (diff)
parent9c94e937fbe8beb56956365cac07d6eff45215cd (diff)
Merge commit 'dkg/master'
Conflicts: doc/MonkeySpec
-rw-r--r--.gitignore3
-rw-r--r--COPYING696
-rw-r--r--Makefile12
-rw-r--r--debian/changelog7
-rw-r--r--debian/compat1
-rw-r--r--debian/control22
-rw-r--r--debian/copyright23
-rw-r--r--debian/dirs11
-rw-r--r--debian/monkeysphere.dirs5
-rw-r--r--debian/monkeysphere.docs2
-rw-r--r--debian/monkeysphere.install7
-rw-r--r--debian/monkeysphere.manpages4
-rwxr-xr-xdebian/rules3
-rw-r--r--doc/MonkeySpec95
-rw-r--r--doc/README73
-rw-r--r--doc/TODO51
-rw-r--r--etc/monkeysphere-server.conf24
-rw-r--r--etc/monkeysphere.conf29
-rw-r--r--gpg2ssh/Makefile16
-rw-r--r--gpg2ssh/gpg2ssh.c291
-rw-r--r--gpg2ssh/main.c271
-rw-r--r--gpg2ssh/ssh2gpg.c171
-rwxr-xr-xhowler/howler134
-rw-r--r--langur/README4
-rw-r--r--man/man1/monkeysphere-ssh-proxycommand.141
-rw-r--r--man/man1/monkeysphere.1113
-rw-r--r--man/man1/openpgp2ssh.191
-rw-r--r--man/man8/monkeysphere-server.883
-rw-r--r--monkeysphere.conf15
-rw-r--r--rhesus/README30
-rwxr-xr-xrhesus/rhesus323
-rw-r--r--src/common566
-rw-r--r--src/keytrans/Makefile12
-rw-r--r--src/keytrans/gnutls-helpers.c (renamed from gpg2ssh/gnutls-helpers.c)164
-rw-r--r--src/keytrans/gnutls-helpers.h (renamed from gpg2ssh/gnutls-helpers.h)8
-rw-r--r--src/keytrans/openpgp2ssh.c437
-rwxr-xr-xsrc/monkeysphere214
-rwxr-xr-xsrc/monkeysphere-server271
-rwxr-xr-xsrc/monkeysphere-ssh-proxycommand56
-rwxr-xr-xsrc/seckey2sshagent25
-rw-r--r--test.key27
41 files changed, 3018 insertions, 1413 deletions
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..36b1d08
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,696 @@
+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 <jrollins@fifthhorseman.net>
+ Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ Jamie McClelland <jamie@mayfirst.org>
+ Micah Anderson <micah@riseup.net>
+ Matthew Goins <mjgoins@openflows.com>
+ Mike Castleman <mlcastle@mlcastle.net>
+ Elliot Winard <enw@caveteen.com>
+ Greg Lyle <greg@stealthisemail.com>
+
+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. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..64e6cbe
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+all: keytrans
+
+keytrans:
+ $(MAKE) -C src/keytrans
+
+release: clean
+ tar c COPYING doc etc Makefile man src | gzip -n > ../monkeysphere_`head -n1 debian/changelog | sed 's/.*(\([^-]*\)-.*/\1/'`.orig.tar.gz
+
+clean:
+ $(MAKE) -C src/keytrans clean
+
+.PHONY: all clean release
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..ec744e1
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,7 @@
+monkeysphere (0.1-1) unstable; urgency=low
+
+ * First release of debian package for monkeysphere.
+ * This is experimental -- please report bugs!
+
+ -- Daniel Kahn Gillmor <dkg-debian.org@fifthhorseman.net> Fri, 13 Jun 2008 10:53:43 -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 <dkg-debian.org@fifthhorseman.net>
+Uploaders: Jameson Rollins <jrollins@fifthhorseman.net>
+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..040e6c8
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,23 @@
+Format-Specification: http://wiki.debian.org/Proposals/CopyrightFormat
+Debianized-By: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+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 <jrollins@fifthhorseman.net>,
+ Daniel Kahn Gillmor <dkg@fifthhorseman.net>,
+ Jamie McClelland <jamie@mayfirst.org>,
+ Micah Anderson <micah@riseup.net>,
+ Matthew Goins <mjgoins@openflows.com>,
+ Mike Castleman <mlcastle@mlcastle.net>,
+ Elliot Winard <enw@caveteen.com>,
+ Greg Lyle <greg@stealthisemail.com>
+
+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..bc8abcf
--- /dev/null
+++ b/debian/monkeysphere.dirs
@@ -0,0 +1,5 @@
+usr/share/monkeysphere
+var/cache/monkeysphere
+var/cache/monkeysphere/authorized_keys
+etc/monkeysphere
+etc/monkeysphere/authorized_user_ids
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 b0a0d6a..54aaa72 100644
--- a/doc/MonkeySpec
+++ b/doc/MonkeySpec
@@ -39,16 +39,16 @@ 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": script to trigger rhesus during attempt to initiate
- connection from client
+* "tamarin": concept - how to trigger or schedule rhesus at admin defined
+ points (e.g. via cron or during ssh connections).
client-side components
----------------------
-* "marmoset": script to trigger rhesus during attempt to initiate
+* "marmoset": concept - how to trigger rhesus during attempt to initiate
connection to server
- runs on connection to a certain host
- triggers update to known_hosts file then makes connection
@@ -63,40 +63,59 @@ Backstory: http://www.conceptlabs.co.uk/alicebob.html
Bob wants to sign on to the computer "mangabey.example.org" via
monkeysphere framework. He doesn't yet have access to the machine,
-but he knows Alice, who is the admin of magabey. Alice and Bob, being
-the conscientious netizens that they are, have already published their
-personal gpg keys to the web of trust, and being good friends, have
-both signed each other's keys and marked each others keys with "full"
-trust.
-
-Alice uses howler to publish a gpg key for magabey with the special
-userid of "ssh://mangabey.example.org". Alice signs mangabey's gpg
-key and publishes this signature as a certification. Alice then
-creates a user "bob" on mangabey, and puts Bob's userid in the
-auth_user_ids file for user bob on magabey. tamarin triggers on
-mangabey, which invokes rhesus. rhesus takes all userids in bob's
-auth_user_ids file, looks on a keyserver to find the public keys for
-each user, converts the gpg public keys into ssh public keys if the
-key validity is acceptable, and finally inserts those keys into an
-authorized_keys file for bob.
-
-Bob now adds the "ssh://mangabey.example.org" userid to the
-auth_host_ids file in his account on his localhost. Bob now goes to
-connect to bob@mangabey.example.org. Bob's monkeysphere-enabled ssh
-client triggers marmoset, which invokes rhesus on Bob's computer.
-rhesus takes all server userids in his auth_host_ids file, looks on a
-keyserver to find the public key for each server (based on the
-server's URI), converts the gpg public keys into ssh public keys if
-the key validity is acceptable, and finally insert those keys into
-Bob's known_hosts file.
-
-On Bob's side, since mangabey's key had "full" validity (since it was
-signed by Alice whom he fully trusts), Bob's ssh client deems mangabey
+but he knows Alice, who is the admin of mangabey. Alice and Bob,
+being the conscientious netizens that they are, have already published
+their personal gpg keys to the web of trust, and being good friends,
+have both signed each other's keys and marked each others keys with
+"full" ownertrust.
+
+When Alice set up mangabey initially, she used howler to publish a gpg
+key for the machine with the special userid of
+"ssh://mangabey.example.org". She also signed mangabey's gpg key and
+published this certification to commonly-used keyservers. Alice also
+configured mangabey to treat her own key with full ownertrust (could
+this be done as part of the howler invocation?)
+
+Now, Alice creates a user account "bob" on mangabey, and puts Bob's
+userid ("Bob <bob@example.org>") in the authorized_user_ids file for
+user bob on mangabey. tamarin triggers on mangabey either by a
+cronjob or an inotify hook, and invokes rhesus for the "bob" account.
+rhesus automatically takes each userid in bob's authorized_user_ids
+file, and looks on a keyserver to find all public keys associated with
+that user ID, with the goal of populating the authorized_keys file for
+bob@mangabey.
+
+In particular: for each key found, the server evaluates the calculated
+validity of the specified user ID based on the ownertrust rules it has
+configured ("trust alice's certifications fully", in this example).
+For each key for which the user ID in question is fully-valid, it
+extracts all DSA- or RSA-based primary or secondary keys marked with
+usage flags for encrypted communications and authentication, and
+converts these gpg public keys into ssh public keys. Finally, rhesus
+inserts these calculated public keys into the authorized_keys file for
+bob.
+
+Bob now attempts to connect, by firing up a terminal and invoking:
+"ssh bob@mangabey.example.org". Bob's monkeysphere-enabled ssh client
+notices that mangabey.example.org isn't already available in bob's
+known_hosts file, and triggers rhesus (on Bob's computer) to fetch the
+key for mangabey, with the goal of populating Bob's local known_hosts
+file.
+
+In particular: rhesus queries its configured keyservers to find all
+public keys with User ID ssh://mangabey.example.org. For each public
+key found, rhesus checks the relevant User ID's validity, converts any
+"encrypted comms, authentication" gpg public keys into ssh public keys
+if the User ID validity is acceptable, and finally insert those keys
+into Bob's known_hosts file.
+
+On Bob's side, since mangabey's key had "full" validity (it was signed
+by Alice whom he fully trusts), Bob's ssh client deems mangabey
"known" and no further host key checking is required.
-On mangabey's side, since Bob's key has "full" validity (since it had
-also been signed by Alice, mangabey's trusted administrator), Bob is
-authenticated and authorized to log into bob@mangabey.
+On mangabey's side, since Bob's key has "full" validity (it had been
+signed by Alice, mangabey's trusted administrator), Bob is
+authenticated and therefore authorized to log into his account.
NOTES
=====
diff --git a/doc/README b/doc/README
index d8f1897..427f214 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_file/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 bob
diff --git a/doc/TODO b/doc/TODO
new file mode 100644
index 0000000..905d198
--- /dev/null
+++ b/doc/TODO
@@ -0,0 +1,51 @@
+Next-Steps Monkeysphere Projects:
+---------------------------------
+
+Handle unknown hosts in such a way that they're not always removed
+ from known_hosts file. Ask user to lsign the host key?
+
+Handle multiple multiple hostnames (multiple user IDs?) when
+ generating host keys with gen-key.
+
+Make sure alternate ports are handled for known_hosts.
+
+Add environment variables sections to man pages.
+
+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.
diff --git a/etc/monkeysphere-server.conf b/etc/monkeysphere-server.conf
new file mode 100644
index 0000000..3915bf4
--- /dev/null
+++ b/etc/monkeysphere-server.conf
@@ -0,0 +1,24 @@
+# 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"
+
+# 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..17c1a14
--- /dev/null
+++ b/etc/monkeysphere.conf
@@ -0,0 +1,29 @@
+# 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="e 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
diff --git a/gpg2ssh/Makefile b/gpg2ssh/Makefile
deleted file mode 100644
index aa18aaa..0000000
--- a/gpg2ssh/Makefile
+++ /dev/null
@@ -1,16 +0,0 @@
-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
-
-.PHONY: clean
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 <gnutls/openpgp.h>
-#include <gnutls/x509.h>
-
-/* for waitpid() */
-#include <sys/types.h>
-#include <sys/wait.h>
-
-/*
- Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
- 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 <gnutls/openpgp.h>
-#include <gnutls/x509.h>
-
-/*
- Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
- 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 <gnutls/openpgp.h>
-#include <gnutls/x509.h>
-
-/* for waitpid() */
-#include <sys/types.h>
-#include <sys/wait.h>
-
-/* for time() */
-#include <time.h>
-
-/* for htons() */
-#include <arpa/inet.h>
-
-
-/*
- Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
- 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 <jrollins@fifthhorseman.net>
-#
-# Copyright 2008, released under the GPL, version 3 or later
-
-PGRM=$(basename $0)
-
-########################################################################
-# FUNCTIONS
-########################################################################
-
-usage() {
-cat <<EOF
-usage: $PGRM gen-key
- $PGRM publish-key
- $PGRM trust-key KEYID [KEYID...]
- $PGRM help
-EOF
-}
-
-failure() {
- echo "$1" >&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 <<EOF
-Key-Type: $KEY_TYPE
-Key-Length: $KEY_LENGTH
-Key-Usage: $KEY_USAGE
-Name-Real: $USERID
-EOF
-
- read -p "generate key? [Y|n]: " OK; OK=${OK:=Y}
- if [ ${OK/y/Y} != 'Y' ] ; then
- failure "aborting."
- fi
-
- if gpg --list-key ="$USERID" > /dev/null 2>&1 ; then
- failure "key for '$USERID' already exists"
- fi
-
- echo "generating server key..."
- gpg --batch --gen-key <<EOF
-Key-Type: $KEY_TYPE
-Key-Length: $KEY_LENGTH
-Key-Usage: $KEY_USAGE
-Name-Real: $USERID
-%commit
-EOF
-}
-
-publish_key() {
- read -p "publish key to $KEYSERVER? [Y|n]: " OK; OK=${OK:=Y}
- if [ ${OK/y/Y} != 'Y' ] ; then
- failure "aborting."
- fi
-
- keyID=$(gpg --list-key --with-colons ="$USERID" 2> /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..5fabb91
--- /dev/null
+++ b/man/man1/monkeysphere-ssh-proxycommand.1
@@ -0,0 +1,41 @@
+.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 is very simple, and can easily be incorporated into other
+ProxyCommand scripts. It first tests to see if the host is in the
+known_hosts file. If it's not, the CHECK_KEYSERVER variable is set to
+true and "update-known_hosts" is run for the host to check for a host
+key for that host. If the host is found in the known_hosts file,
+CHECK_KEYSERVER is set to false and "update-known_hosts" is run to
+update from the local keychain.
+
+Run the following command for more info:
+
+.B less $(which monkeysphere-ssh-proxycommand)
+
+.SH AUTHOR
+
+Written by Jameson Rollins <jrollins@fifthhorseman.net>
+
+.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..a5b422c
--- /dev/null
+++ b/man/man1/monkeysphere.1
@@ -0,0 +1,113 @@
+.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 and encryption of ssh connection.
+
+\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-userids [USERID]...
+Add/update a user ID to the authorized_user_ids file. The user IDs
+specified should be exact matches to OpenPGP user IDs. For each
+specified user ID, gpg will be queried for a key associated with that
+user ID, querying a keyserver if specified. If a key is found, the
+user ID will be added to the user's authorized_user_ids file (if it
+wasn't already present). `u' may be used in place of
+`update-userids'.
+.TP
+.B remove-userids [USERID]...
+Remove a user ID from the authorized_user_ids file. The user IDs
+specified should be exact matches to OpenPGP user IDs. `r' may be
+used in place of `remove-userids'.
+.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
+For host keys, the key must have both the "authentication" ("a") and
+"encrypt" ("e") capability flags. For user keys, the key must have
+the "authentication" ("a") capability flag.
+.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.
+.TP
+~/.config/monkeysphere/authorized_keys
+Monkeysphere generated authorized_keys file.
+
+.SH AUTHOR
+
+Written by Jameson Rollins <jrollins@fifthhorseman.net>
+
+.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
+<dkg@fifthhorseman.net>.
+.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..5ca248a
--- /dev/null
+++ b/man/man8/monkeysphere-server.8
@@ -0,0 +1,83 @@
+.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 and encryption of ssh connection.
+
+\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, update 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 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 update-user-userids USER USERID...
+Add/update a user ID to the authorized_user_ids file for USER. `u' may
+be used in place of `update-user-userids'.
+.TP
+.B remove-user-userids USER USERID...
+Remove a user ID from the authorized_user_ids file for USER. `r' may
+be used in place of `remove-user-userids'.
+.TP
+.B help
+Output a brief usage summary. `h' or `?' may be used in place of
+`help'.
+
+.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/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 <jrollins@fifthhorseman.net>
+
+.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 <jrollins@fifthhorseman.net>
-#
-# Copyright 2008, released under the GPL, version 3 or later
-
-PGRM=$(basename $0)
-
-########################################################################
-# FUNCTIONS
-########################################################################
-
-usage() {
-cat <<EOF
-usage: $PGRM k|known_hosts [userid...]
- $PGRM a|authorized_keys [userid...]
-Monkeysphere update of known_hosts or authorized_keys file.
-If userids are specified, only specified userids will be processed
-(userids must be included in the appropriate auth_*_ids file).
-EOF
-}
-
-failure() {
- echo "$1" >&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..7a90453
--- /dev/null
+++ b/src/common
@@ -0,0 +1,566 @@
+# -*-shell-script-*-
+
+# Shared sh functions for the monkeysphere
+#
+# Written by
+# Jameson Rollins <jrollins@fifthhorseman.net>
+#
+# 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
+
+########################################################################
+### UTILITY FUNCTIONS
+
+failure() {
+ echo "$1" >&2
+ exit ${2:-'1'}
+}
+
+# write output to stderr
+log() {
+ echo -n "ms: " 1>&2
+ echo "$@" 1>&2
+}
+
+loge() {
+ echo "$@" 1>&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_file_line() {
+ local file
+ local string
+
+ file="$1"
+ string="$2"
+
+ if [ "$file" -a "$string" ] ; then
+ grep -v "$string" "$file" | sponge "$file"
+ fi
+}
+
+### 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 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 "$keyID" | grep "$keyID" | \
+ grep '^fpr:' | 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
+}
+
+# update the cache for userid, and prompt to add file to
+# authorized_user_ids file if the userid is found in gpg
+# and not already in file.
+update_userid() {
+ local userID
+
+ userID="$1"
+
+ log "processing userid: '$userID'"
+
+ # process the user ID to pull it from keyserver
+ process_user_id "$userID" | grep -q "^0 "
+
+ # check if user ID is in the authorized_user_ids file
+ if ! grep -q "^${userID}\$" "$AUTHORIZED_USER_IDS" ; then
+ read -p "user ID not currently authorized. authorize? [Y|n]: " OK; OK=${OK:=Y}
+ if [ ${OK/y/Y} = 'Y' ] ; then
+ # add if specified
+ log -n " adding user ID to authorized_user_ids file... "
+ echo "$userID" >> "$AUTHORIZED_USER_IDS"
+ loge "done."
+ else
+ # else do nothing
+ log " authorized_user_ids file untouched."
+ fi
+ fi
+}
+
+# remove a userid from the authorized_user_ids file
+remove_userid() {
+ local userID
+
+ userID="$1"
+
+ log "processing userid: '$userID'"
+
+ # check if user ID is in the authorized_user_ids file
+ if ! grep -q "^${userID}\$" "$AUTHORIZED_USER_IDS" ; then
+ log " user ID not currently authorized."
+ return 1
+ fi
+
+ # remove user ID from file
+ log -n " removing user ID '$userID'... "
+ remove_file_line "$AUTHORIZED_USER_IDS" "^${userID}$"
+ loge "done."
+}
+
+# process a host in known_host file
+process_host_known_hosts() {
+ local host
+ local userID
+ local ok
+ local keyid
+ local tmpfile
+
+ host="$1"
+ userID="ssh://${host}"
+
+ log "processing host: $host"
+
+ process_user_id "ssh://${host}" | \
+ while read -r ok keyid ; do
+ sshKey=$(gpg2ssh "$keyid")
+ # remove the old host key line
+ remove_file_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
+}
+
+# process a uid in an authorized_keys file
+process_uid_authorized_keys() {
+ local userID
+ local ok
+ local keyid
+
+ userID="$1"
+
+ 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_file_line "$AUTHORIZED_KEYS" "$sshKey"
+ # if key OK, add new host line
+ if [ "$ok" -eq '0' ] ; then
+ ssh2authorized_keys "$userID" "$sshKey" >> "$AUTHORIZED_KEYS"
+ fi
+ done
+}
+
+# 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
+ # and process each host
+ for host in ${hosts[*]} ; do
+ process_host_known_hosts "$host"
+ done
+ done
+}
+
+# process an authorized_user_ids file for authorized_keys
+process_authorized_user_ids() {
+ local userid
+
+ cat "$AUTHORIZED_USER_IDS" | meat | \
+ while read -r userid ; do
+ process_uid_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
+ log "could not retrieve key '$keyID'"
+ return 1
+ 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 --send-keys --keyserver "$KEYSERVER" $(hostname -f)
+ echo "NOT PUBLISHED: gpg --send-keys --keyserver $KEYSERVER $(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/gpg2ssh/gnutls-helpers.c b/src/keytrans/gnutls-helpers.c
index 6eae29e..5b4c46a 100644
--- a/gpg2ssh/gnutls-helpers.c
+++ b/src/keytrans/gnutls-helpers.c
@@ -12,11 +12,19 @@
/* for isalnum() */
#include <ctype.h>
-int loglevel = 0;
+/* for exit() */
+#include <unistd.h>
+#include <assert.h>
-void err(const char* fmt, ...) {
+/* 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);
@@ -35,44 +43,118 @@ void init_keyid(gnutls_openpgp_keyid_t keyid) {
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 kix = 0, outix = 0;
+ unsigned int inix = 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++;
+ 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("Failed to do gnutls_global_init() (error: %d)\n", ret);
+ err(0, "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);
+ err(1, "gnutls version: %s\n", version);
else {
- err("no version found!\n");
+ err(0, "no gnutls 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);
- }
+ gnutls_global_set_log_function(logfunc);
+
+ gnutls_global_set_log_level(loglevel);
+ err(1, "set log level to %d\n", loglevel);
+
return 0;
}
@@ -87,11 +169,11 @@ 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) {
if (a->size > b->size) {
- err("a is larger\n");
+ err(0,"a is larger\n");
return 1;
}
if (a->size < b->size) {
- err("b is larger\n");
+ err(0,"b is larger\n");
return -1;
}
return memcmp(a->data, b->data, a->size);
@@ -127,7 +209,7 @@ int set_datum_fd(gnutls_datum_t* d, int fd) {
bufsize = 1024;
d->data = gnutls_realloc(d->data, bufsize);
if (d->data == NULL) {
- err("out of memory!\n");
+ err(0,"out of memory!\n");
return -1;
}
d->size = bufsize;
@@ -136,7 +218,7 @@ int set_datum_fd(gnutls_datum_t* d, int fd) {
}
f = fdopen(fd, "r");
if (NULL == f) {
- err("could not fdopen FD %d\n", fd);
+ err(0,"could not fdopen FD %d\n", fd);
}
clearerr(f);
while (!feof(f) && !ferror(f)) {
@@ -145,16 +227,16 @@ int set_datum_fd(gnutls_datum_t* d, int fd) {
bufsize *= 2;
d->data = gnutls_realloc(d->data, bufsize);
if (d->data == NULL) {
- err("out of memory!\n");
+ err(0,"out of memory!\n");
return -1;
};
d->size = bufsize;
}
len += fread(d->data + len, 1, bufsize - len, f);
- /* err("read %d bytes\n", len); */
+ /* err(0,"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));
+ err(0,"Error reading from fd %d (error: %d) (error: %d '%s')\n", fd, ferror(f), errno, strerror(errno));
return -1;
}
@@ -173,13 +255,13 @@ int set_datum_file(gnutls_datum_t* d, const char* fname) {
size_t x = 0;
if (0 != stat(fname, &sbuf)) {
- err("failed to stat '%s'\n", fname);
+ err(0,"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);
+ err(0,"failed to allocate %d bytes for '%s'\n", sbuf.st_size, fname);
return -1;
}
@@ -187,13 +269,13 @@ int set_datum_file(gnutls_datum_t* d, const char* fname) {
d->size = sbuf.st_size;
file = fopen(fname, "r");
if (NULL == file) {
- err("failed to open '%s' for reading\n", fname);
+ err(0,"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);
+ err(0,"tried to read %d bytes, read %d instead from '%s'\n", d->size, x, fname);
fclose(file);
return -1;
}
@@ -203,7 +285,7 @@ int set_datum_file(gnutls_datum_t* d, const char* fname) {
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");
+ err(0,"failed to write body of datum.\n");
return -1;
}
return 0;
@@ -225,12 +307,12 @@ int write_datum_fd_with_length(int fd, const gnutls_datum_t* d) {
len = htonl(d->size);
}
if (write(fd, &len, sizeof(len)) != sizeof(len)) {
- err("failed to write size of datum.\n");
+ err(0,"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");
+ err(0,"failed to write padding byte for MPI.\n");
return -2;
}
}
@@ -264,29 +346,29 @@ int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]) {
int ret;
if (pid == NULL) {
- err("bad pointer passed to create_writing_pipe()\n");
+ err(0,"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));
+ err(0,"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));
+ 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("Failed to transfer reading file descriptor to stdin (error: %d \"%s\")\n", errno, strerror(errno));
+ err(0,"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));
+ err(0,"exec %s failed (error: %d \"%s\")\n", path, errno, strerror(errno));
/* close the open file descriptors */
close(p[0]);
close(0);
@@ -304,14 +386,14 @@ int validate_ssh_host_userid(const char* userid) {
/* 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");
+ 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("label did not start with a letter or a digit! (%s)\n", userid);
+ err(0,"label did not start with a letter or a digit! (%s)\n", userid);
goto fail;
}
userid++;
@@ -321,19 +403,19 @@ int validate_ssh_host_userid(const char* userid) {
check last char
isalnum */
if (!isalnum(*(userid - 1))) {
- err("label did not end with a letter or a digit!\n");
+ 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("invalid character in domain name: %c\n", *userid);
+ err(0,"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");
+ 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
diff --git a/gpg2ssh/gnutls-helpers.h b/src/keytrans/gnutls-helpers.h
index 9ea22a3..f196456 100644
--- a/gpg2ssh/gnutls-helpers.h
+++ b/src/keytrans/gnutls-helpers.h
@@ -23,7 +23,7 @@ int init_gnutls();
/* logging and output functions: */
-void err(const char* fmt, ...);
+void err(int level, const char* fmt, ...);
void logfunc(int level, const char* string);
/* basic datum manipulations: */
@@ -44,6 +44,12 @@ 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: */
diff --git a/src/keytrans/openpgp2ssh.c b/src/keytrans/openpgp2ssh.c
new file mode 100644
index 0000000..511af71
--- /dev/null
+++ b/src/keytrans/openpgp2ssh.c
@@ -0,0 +1,437 @@
+#include "gnutls-helpers.h"
+
+#include <gnutls/openpgp.h>
+#include <gnutls/x509.h>
+
+/* for waitpid() */
+#include <sys/types.h>
+#include <sys/wait.h>
+
+/*
+ Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ 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 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..6853f58
--- /dev/null
+++ b/src/monkeysphere
@@ -0,0 +1,214 @@
+#!/bin/bash
+
+# monkeysphere: MonkeySphere client tool
+#
+# The monkeysphere scripts are written by:
+# Jameson Rollins <jrollins@fifthhorseman.net>
+#
+# 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 <<EOF
+usage: $PGRM <subcommand> [args]
+MonkeySphere client tool.
+
+subcommands:
+ update-known_hosts (k) [HOST]... update known_hosts file
+ update-userids (u) [USERID]... add/update user IDs
+ remove-userids (r) [USERID]... remove user IDs
+ 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)
+
+ # return 1 if there only "tru" lines are output from gpg
+ if [ -z "$(echo "$gpgOut" | grep -v '^tru:')" ] ; then
+ failure "Key ID '$keyID' not found."
+ fi
+
+ # set subkey defaults
+ SUBKEY_TYPE=${SUBKEY_TYPE:-"RSA"}
+ #SUBKEY_LENGTH=${SUBKEY_LENGTH:-"2048"}
+ SUBKEY_USAGE=${SUBKEY_USAGE:-"auth"}
+ SUBKEY_EXPIRE=${SUBKEY_EXPIRE:-"0"}
+ cat <<EOF
+Please specify how long the key should be valid.
+ 0 = key does not expire
+ <n> = key expires in n days
+ <n>w = key expires in n weeks
+ <n>m = key expires in n months
+ <n>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 <<EOF
+addkey
+7
+S
+E
+A
+Q
+$SUBKEY_LENGTH
+$SUBKEY_EXPIRE
+save
+EOF
+)
+
+ log "generating subkey..."
+ echo "$editCommands" | gpg --expert --command-fd 0 --edit-key "$keyID"
+ log "done."
+}
+
+########################################################################
+# MAIN
+########################################################################
+
+COMMAND="$1"
+[ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
+shift
+
+# 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 empty config variable with defaults
+AUTHORIZED_USER_IDS=${AUTHORIZED_USER_IDS:-"${MS_HOME}/authorized_user_ids"}
+GNUPGHOME=${GNUPGHOME:-"${HOME}/.gnupg"}
+KEYSERVER=${KEYSERVER:-"subkeys.pgp.net"}
+CHECK_KEYSERVER=${CHECK_KEYSERVER:="true"}
+REQUIRED_HOST_KEY_CAPABILITY=${REQUIRED_HOST_KEY_CAPABILITY:-"e a"}
+REQUIRED_USER_KEY_CAPABILITY=${REQUIRED_USER_KEY_CAPABILITY:-"a"}
+KNOWN_HOSTS=${KNOWN_HOSTS:-"${HOME}/.ssh/known_hosts"}
+AUTHORIZED_KEYS=${AUTHORIZED_KEYS:-"${HOME}/.ssh/authorized_keys"}
+HASH_KNOWN_HOSTS=${HASH_KNOWN_HOSTS:-"true"}
+
+export GNUPGHOME
+
+# make sure gpg home exists with proper permissions
+mkdir -p -m 0700 "$GNUPGHOME"
+
+# make sure the user monkeysphere home directory exists
+mkdir -p -m 0700 "$MS_HOME"
+touch "$AUTHORIZED_USER_IDS"
+touch "$AUTHORIZED_KEYS"
+
+case $COMMAND in
+ 'update-known_hosts'|'update-known-hosts'|'k')
+ MODE='known_hosts'
+
+ # touch the known_hosts file to make sure it exists
+ # ssh-keygen complains if it doesn't exist
+ touch "$KNOWN_HOSTS"
+
+ # if hosts are specified on the command line, process just
+ # those hosts
+ if [ "$1" ] ; then
+ for host ; do
+ process_host_known_hosts "$host"
+ done
+ log "known_hosts file updated."
+
+ # otherwise, if no hosts are specified, process every user
+ # in the user's known_hosts file
+ else
+ if [ ! -s "$KNOWN_HOSTS" ] ; then
+ failure "known_hosts file '$KNOWN_HOSTS' is empty."
+ fi
+ log "processing known_hosts file..."
+ process_known_hosts
+ log "known_hosts file updated."
+ fi
+ ;;
+
+ 'update-userids'|'update-userid'|'u')
+ if [ -z "$1" ] ; then
+ failure "you must specify at least one userid."
+ fi
+ for userID ; do
+ update_userid "$userID"
+ done
+ log "Run the following to update your monkeysphere authorized_keys file:"
+ log "$PGRM update-authorized_keys"
+ ;;
+
+ 'remove-userids'|'remove-userid'|'r')
+ if [ -z "$1" ] ; then
+ failure "you must specify at least one userid."
+ fi
+ for userID ; do
+ remove_userid "$userID"
+ done
+ log "Run the following to update your monkeysphere authorized_keys file:"
+ log "$PGRM update-authorized_keys"
+ ;;
+
+ 'update-authorized_keys'|'update-authorized-keys'|'a')
+ MODE='authorized_keys'
+
+ # fail if the authorized_user_ids file is empty
+ if [ ! -s "$AUTHORIZED_USER_IDS" ] ; then
+ failure "$AUTHORIZED_USER_IDS is empty."
+ fi
+
+ # process authorized_user_ids file
+ log "processing authorized_user_ids file..."
+ process_authorized_user_ids
+ log "authorized_keys file updated."
+ ;;
+
+ 'gen-subkey'|'g')
+ keyID="$1"
+ if [ -z "$keyID" ] ; then
+ failure "You must specify the key ID of your primary key."
+ fi
+ gen_subkey "$keyID"
+ ;;
+
+ 'help'|'h'|'?')
+ usage
+ ;;
+
+ *)
+ failure "Unknown command: '$COMMAND'
+Type '$PGRM help' for usage."
+ ;;
+esac
diff --git a/src/monkeysphere-server b/src/monkeysphere-server
new file mode 100755
index 0000000..560d249
--- /dev/null
+++ b/src/monkeysphere-server
@@ -0,0 +1,271 @@
+#!/bin/bash
+
+# monkeysphere-server: MonkeySphere server admin tool
+#
+# The monkeysphere scripts are written by:
+# Jameson Rollins <jrollins@fifthhorseman.net>
+#
+# 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 <<EOF
+usage: $PGRM <subcommand> [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
+ publish-key (p) publish server key to keyserver
+ trust-keys (t) KEYID... mark keyids as trusted
+ update-user-userids (u) USER UID... add/update user IDs for a user
+ remove-user-userids (r) USER UID... remove user IDs for a user
+ help (h,?) this help
+
+EOF
+}
+
+# generate server gpg key
+gen_key() {
+ local hostName
+
+ hostName=${1:-$(hostname --fqdn)}
+
+ # set key defaults
+ KEY_TYPE=${KEY_TYPE:-"RSA"}
+ KEY_LENGTH=${KEY_LENGTH:-"2048"}
+ KEY_USAGE=${KEY_USAGE:-"auth,encrypt"}
+ cat <<EOF
+Please specify how long the key should be valid.
+ 0 = key does not expire
+ <n> = key expires in n days
+ <n>w = key expires in n weeks
+ <n>m = key expires in n months
+ <n>y = key expires in n years
+EOF
+ read -p "Key is valid for? ($EXPIRE) " EXPIRE; EXPIRE=${EXPIRE:-"0"}
+
+ SERVICE=${SERVICE:-"ssh"}
+ USERID=${USERID:-"$SERVICE"://"$hostName"}
+
+ # set key parameters
+ keyParameters=$(cat <<EOF
+Key-Type: $KEY_TYPE
+Key-Length: $KEY_LENGTH
+Key-Usage: $KEY_USAGE
+Name-Real: $USERID
+Expire-Date: $EXPIRE
+EOF
+)
+
+ # add the revoker field if requested
+ if [ "$REVOKER" ] ; then
+ keyParameters="${keyParameters}"$(cat <<EOF
+
+Revoker: 1:$REVOKER sensitive
+EOF
+)
+ fi
+
+ echo "The following key parameters will be used:"
+ echo "$keyParameters"
+
+ read -p "generate key? [Y|n]: " OK; OK=${OK:=Y}
+ if [ ${OK/y/Y} != 'Y' ] ; then
+ failure "aborting."
+ fi
+
+ if gpg --list-key ="$USERID" > /dev/null 2>&1 ; then
+ failure "key for '$USERID' already exists"
+ fi
+
+ # add commit command
+ keyParameters="${keyParameters}"$(cat <<EOF
+
+%commit
+%echo done
+EOF
+)
+
+ log -n "generating server key... "
+ echo "$keyParameters" | gpg --batch --gen-key
+ loge "done."
+}
+
+########################################################################
+# 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"}
+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
+ unames="$@"
+ else
+ unames=$(ls -1 "${MS_HOME}/authorized_user_ids")
+ fi
+
+ for uname in $unames ; do
+ MODE="authorized_keys"
+
+ log "----- user: $uname -----"
+
+ # set variables for the user
+ AUTHORIZED_USER_IDS="${MS_HOME}/authorized_user_ids/${uname}"
+ # temporary authorized_keys file
+ AUTHORIZED_KEYS="${CACHE}/authorized_keys/${uname}.tmp"
+
+ # make sure user's authorized_user_ids file exists
+ touch "$AUTHORIZED_USER_IDS"
+ # make sure the authorized_keys file exists and is clear
+ > "$AUTHORIZED_KEYS"
+
+ # skip if the user's authorized_user_ids file is empty
+ if [ ! -s "$AUTHORIZED_USER_IDS" ] ; then
+ log "authorized_user_ids file for '$uname' is empty."
+ continue
+ fi
+
+ # process authorized_user_ids file
+ log "processing authorized_user_ids file..."
+ process_authorized_user_ids
+
+ # add user-controlled authorized_keys file path if specified
+ if [ "$USER_CONTROLLED_AUTHORIZED_KEYS" != '-' ] ; then
+ userHome=$(getent passwd "$uname" | cut -d: -f6)
+ userAuthorizedKeys=${USER_CONTROLLED_AUTHORIZED_KEYS/\%h/"$userHome"}
+ log -n "adding user's authorized_keys file... "
+ cat "$userAuthorizedKeys" >> "$AUTHORIZED_KEYS"
+ loge "done."
+ fi
+
+ # move the temp authorized_keys file into place
+ mv -f "${CACHE}/authorized_keys/${uname}.tmp" "${CACHE}/authorized_keys/${uname}"
+
+ log "authorized_keys file updated."
+ done
+
+ log "----- done. -----"
+ ;;
+
+ 'gen-key'|'g')
+ gen_key "$1"
+ ;;
+
+ '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
+ ;;
+
+ 'update-user-userids'|'update-user-userid'|'u')
+ uname="$1"
+ shift
+ if [ -z "$uname" ] ; then
+ failure "You must specify user."
+ fi
+ if [ -z "$1" ] ; then
+ failure "You must specify at least one user ID."
+ fi
+
+ # set variables for the user
+ AUTHORIZED_USER_IDS="$MS_HOME"/authorized_user_ids/"$uname"
+
+ # make sure user's authorized_user_ids file exists
+ touch "$AUTHORIZED_USER_IDS"
+
+ # process the user IDs
+ for userID ; do
+ update_userid "$userID"
+ done
+
+ log "Run the following to update user's authorized_keys file:"
+ log "$PGRM update-users $uname"
+ ;;
+
+ 'remove-user-userids'|'remove-user-userid'|'r')
+ uname="$1"
+ shift
+ if [ -z "$uname" ] ; then
+ failure "You must specify user."
+ fi
+ if [ -z "$1" ] ; then
+ failure "You must specify at least one user ID."
+ fi
+
+ # set variables for the user
+ AUTHORIZED_USER_IDS="$MS_HOME"/authorized_user_ids/"$uname"
+
+ # make sure user's authorized_user_ids file exists
+ touch "$AUTHORIZED_USER_IDS"
+
+ # process the user IDs
+ for userID ; do
+ remove_userid "$userID"
+ done
+
+ log "Run the following to update user's authorized_keys file:"
+ log "$PGRM update-users $uname"
+ ;;
+
+ 'help'|'h'|'?')
+ usage
+ ;;
+
+ *)
+ failure "Unknown command: '$COMMAND'
+Type '$PGRM help' for usage."
+ ;;
+esac
diff --git a/src/monkeysphere-ssh-proxycommand b/src/monkeysphere-ssh-proxycommand
new file mode 100755
index 0000000..4b90a0d
--- /dev/null
+++ b/src/monkeysphere-ssh-proxycommand
@@ -0,0 +1,56 @@
+#!/bin/sh -e
+
+# monkeysphere-ssh-proxycommand: MonkeySphere ssh ProxyCommand hook
+#
+# The monkeysphere scripts are written by:
+# Jameson Rollins <jrollins@fifthhorseman.net>
+#
+# 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
+
+HOST="$1"
+PORT="$2"
+
+usage() {
+cat <<EOF >&2
+usage: ssh -o ProxyCommand="$(basename $0) %h %p" ...
+EOF
+}
+
+log() {
+ echo "$@" >&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
+
+# check for the host key in the known_hosts file
+hostKey=$(ssh-keygen -F "$HOST")
+
+# if the host key is found in the known_hosts file,
+# don't check the keyserver
+if [ "$hostKey" ] ; then
+ CHECK_KEYSERVER="false"
+else
+ CHECK_KEYSERVER="true"
+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
+exec nc "$HOST" "$PORT"
diff --git a/src/seckey2sshagent b/src/seckey2sshagent
new file mode 100755
index 0000000..0e8d695
--- /dev/null
+++ b/src/seckey2sshagent
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+cleanup() {
+ echo -n "removing temp gpg home... "
+ rm -rf $FOO
+ echo "done."
+}
+
+trap cleanup EXIT
+
+GPGID="$1"
+
+idchars=$(echo $GPGID | wc -m)
+if [ "$idchars" -ne 17 ] ; then
+ echo "GPGID is not 16 characters ($idchars)."
+ exit 1
+fi
+
+FOO=$(mktemp -d)
+
+gpg --export-secret-key --export-options export-reset-subkey-passwd $GPGID | GNUPGHOME=$FOO gpg --import
+
+GNUPGHOME=$FOO gpg --edit-key $GPGID
+
+GNUPGHOME=$FOO gpg --export-secret-key $GPGID | openpgp2ssh $GPGID | ssh-add -c /dev/stdin
diff --git a/test.key b/test.key
deleted file mode 100644
index 4e05880..0000000
--- a/test.key
+++ /dev/null
@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEogIBAAKCAQEAxMv33LvWBZnKtahorHGYdBZqVxrNUQcVNrgxp4bf/FvgvSLG
-kBrw6wHFdVYvWWViD5efrJugqA4+pKp16LEWlc7JZICrou4vEJGkvoqBIJC/4cVN
-xcwV1a8jo9ZOYjt0JIyuHrEDGW/edQYWI41XO/H+QdMDsdI+oOmfPV/V4eMyjGKH
-vRJ+xDae5izhUb3Lb00YnxpP2n/zhvHpn7weu+bzvwb3pMMo9336Ft7m5ulGPJzN
-+3l595LW+lUSDUlUJbACp4Nyn+i9ODPV6xzghzirsh7rnD8jD2kaqIVkcvEhusoB
-JN3daPXt9t6m5cfsCWu31BXdbpTWiLIZRUxDzQIBIwKCAQEAl9CMAg0+s90KFxuD
-8r4H5IZSCK5GnZe/6GI07vMEj3oTxRrTsP7XG7DoyDsr1z+UyjMjZ+XFE+27S9P0
-ju8Cy1Zg2ICEZ78OXT0nUSkEhtYQXbV2gqTAYwNzQ9/WEUPOn9o9LZ5+u9n0wKzs
-gdNvLj5WbUsC2aIwUD8xswDJkP5cA4RfKo8Mz40aXbK6b+S/bOKEkXRFvOor46pl
-A8GHxUVcUPUG7LAXCm1FWrDob6FTlv3yW8DeVTCYwt6HdrTmc9b+yOinwMR6ZvUz
-R6AESGG7czCvA6rpkCcprfCPx0gfntuzLiGRtl54GvbYWWtPDlxnPwcw1zcSALvM
-pJNpawKBgQD/zze04kYZBNDTxolBrZltpPXtPpOrG2Otp8CHreOKn0TifCFPDnCb
-ewUhxuDRA+L9KPLT311DtHfIzXJ8/RD6K/QE72ny39h2X2Pn2hWSgb9+iysHBDNc
-jb136QFoKQcpqUpLEfTvA71Yqvuk6gsYiuWnIN5KJwy/AhwFQnK/WQKBgQDE8X87
-C+0JSg2ybUopOQVSrvildJEa8CWbM1SAL1j3E24U2fPh+zVmIxqa2m4X/PxFBBTv
-WVGayzFkmJK2Dgt7F7hBqi5HelP0B38dXtkPlK6idTALNHoS/7HCDXISgHmDOhcQ
-LHGQUuQMkTq6H4cOMwTNO5aM2zc5E9uF/hptlQKBgEHHkftQIKdZAn+Zc8Bud+j+
-iGGTv5JmIPIj0mwIJJFcJ6f0CJCr8RIJsNzMvXeTSP9FCz3LuOWGLW4mM2H37mw3
-MB6GtNgNrLC5cXYiIs3m2XhPq/p9bEr/4ENnzSlposGR7ohVExjjtFig/uFDfzIy
-WE+MG+cunOCoxWBwLCKTAoGBALQP/0vtpYTV/eT2NS0A7uyCt3Kzt94dZDYgTUH/
-Z0hMR2OFcUOj2Qzs5R/dpnxVA+dUMGXOAXeVNHk7CcsFhtbxHX3dbCQYEj4yvVyu
-fVAS6M8MDqsoqh//uHbnuMB1dmlZrq+zmwecPjdgNbF76TGNuz9MbGOGmOO6Yk6f
-LhsLAoGAJoK+yRDaEFDwrjdvGazEy/d1CtknkGY2r4vb8giEodFJcQQhtVtjnYPl
-gDIpbcpeT0GDiZd0pxAxpibbKM63pYz8PKtlq0B/qXgArRgJnbku01Jc4iLVWPqK
-qitRgsz1HdN2tIqa8oQE0iuvyoq+r6+pqcQJd7sc6lKlk0gO0Mo=
------END RSA PRIVATE KEY-----