Merge commit 'mjgoins/master'
authorDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Sun, 22 Jun 2008 04:27:16 +0000 (00:27 -0400)
committerDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Sun, 22 Jun 2008 04:27:16 +0000 (00:27 -0400)
47 files changed:
.gitignore
COPYING [new file with mode: 0644]
Makefile [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/dirs [new file with mode: 0644]
debian/monkeysphere.dirs [new file with mode: 0644]
debian/monkeysphere.docs [new file with mode: 0644]
debian/monkeysphere.install [new file with mode: 0644]
debian/monkeysphere.manpages [new file with mode: 0644]
debian/rules [new file with mode: 0755]
doc/MonkeySpec
doc/README
doc/TODO [new file with mode: 0644]
doc/george/changelog [new file with mode: 0644]
doc/george/host-key-publication [new file with mode: 0644]
doc/george/policy [new file with mode: 0644]
doc/george/user-id-configuration [new file with mode: 0644]
etc/monkeysphere-server.conf [new file with mode: 0644]
etc/monkeysphere.conf [new file with mode: 0644]
gpg2ssh/Makefile [deleted file]
gpg2ssh/gnutls-helpers.c [deleted file]
gpg2ssh/gnutls-helpers.h [deleted file]
gpg2ssh/gpg2ssh.c [deleted file]
gpg2ssh/main.c [deleted file]
gpg2ssh/ssh2gpg.c [deleted file]
howler/howler [deleted file]
langur/README [deleted file]
man/man1/monkeysphere-ssh-proxycommand.1 [new file with mode: 0644]
man/man1/monkeysphere.1 [new file with mode: 0644]
man/man1/openpgp2ssh.1 [new file with mode: 0644]
man/man8/monkeysphere-server.8 [new file with mode: 0644]
monkeysphere.conf [deleted file]
rhesus/README [deleted file]
rhesus/rhesus [deleted file]
src/common [new file with mode: 0644]
src/keytrans/Makefile [new file with mode: 0644]
src/keytrans/gnutls-helpers.c [new file with mode: 0644]
src/keytrans/gnutls-helpers.h [new file with mode: 0644]
src/keytrans/openpgp2ssh.c [new file with mode: 0644]
src/monkeysphere [new file with mode: 0755]
src/monkeysphere-server [new file with mode: 0755]
src/monkeysphere-ssh-proxycommand [new file with mode: 0755]
src/seckey2sshagent [new file with mode: 0755]
test.key [deleted file]

index 80bf65d079a0fd09d8b7292fd3b2846f1ab16765..0dc4f7940b1522b90094938ac7d640c7a74603ad 100644 (file)
@@ -1,5 +1,2 @@
 *~
 *.[ao]
-monkeysphere
-gpg2ssh
-ssh2gpg
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..fefe9ab
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,697 @@
+MonkeySphere is a system to use the OpenPGP web-of-trust to
+authenticate and encrypt ssh connections.
+
+It is free software, developed by:
+  Jameson Rollins <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>
+  Ross Glover <ross@ross.mayfirst.org>
+  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 (file)
index 0000000..1a214fc
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,27 @@
+MONKEYSPHERE_VERSION=`head -n1 debian/changelog | sed 's/.*(\([^-]*\)-.*/\1/'`
+
+all: keytrans
+
+keytrans:
+       $(MAKE) -C src/keytrans
+
+release: clean
+       rm -rf monkeysphere-$(MONKEYSPHERE_VERSION)
+       mkdir -p monkeysphere-$(MONKEYSPHERE_VERSION)/doc
+       ln -s ../../doc/README ../../doc/TODO ../../doc/MonkeySpec monkeysphere-$(MONKEYSPHERE_VERSION)/doc
+       ln -s ../COPYING ../etc ../Makefile ../man ../src  monkeysphere-$(MONKEYSPHERE_VERSION)
+       tar -ch monkeysphere-$(MONKEYSPHERE_VERSION) | gzip -n > monkeysphere_$(MONKEYSPHERE_VERSION).orig.tar.gz
+       rm -rf monkeysphere-$(MONKEYSPHERE_VERSION)
+
+debian-package: release
+       tar xzf monkeysphere_$(MONKEYSPHERE_VERSION).orig.tar.gz
+       cp -a debian monkeysphere-$(MONKEYSPHERE_VERSION)
+       (cd monkeysphere-$(MONKEYSPHERE_VERSION) && debuild -uc -us)
+       rm -rf monkeysphere-$(MONKEYSPHERE_VERSION)
+
+clean:
+       $(MAKE) -C src/keytrans clean
+       # clean up old monkeysphere packages lying around as well.
+       rm -f monkeysphere_*
+
+.PHONY: all clean release debian-package
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..cec0988
--- /dev/null
@@ -0,0 +1,24 @@
+monkeysphere (0.2-1) UNRELEASED; urgency=low
+
+  [ Daniel Kahn Gillmor ]
+  * NOT YET RELEASED (switch to "experimental" when ready to release)
+
+  [ Jameson Graef Rollins ]
+  * Add AUTHORIZED_USER_IDS config variable for server, which defaults to
+    %h/.config/monkeysphere/authorized_user_ids, instead of
+    /etc/monkeysphere/authorized_user_ids.
+  * Remove {update,remove}-userids functions, since we decided they
+    weren't useful enough to be worth maintaining.
+  * Better handling of unknown users in server update-users
+  * Add file locking when modifying known_hosts or authorized_keys
+  * Better failure/prompting for gen-subkey
+
+ -- Jameson Graef Rollins <jrollins@phys.columbia.edu>  Sat, 21 Jun 2008 16:39:26 -0400
+
+monkeysphere (0.1-1) experimental; urgency=low
+
+  * First release of debian package for monkeysphere.
+  * This is experimental -- please report bugs!
+
+ -- Daniel Kahn Gillmor <dkg-debian.org@fifthhorseman.net>  Thu, 19 Jun 2008 00:34:53 -0400
+
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..7f8f011
--- /dev/null
@@ -0,0 +1 @@
+7
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..d4d25c6
--- /dev/null
@@ -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 (file)
index 0000000..1fa6d83
--- /dev/null
@@ -0,0 +1,24 @@
+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>,
+ Ross Glover <ross@ross.mayfirst.org>,
+ 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 (file)
index 0000000..b458649
--- /dev/null
@@ -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 (file)
index 0000000..6e90899
--- /dev/null
@@ -0,0 +1,4 @@
+usr/share/monkeysphere
+var/cache/monkeysphere
+var/cache/monkeysphere/authorized_keys
+etc/monkeysphere
diff --git a/debian/monkeysphere.docs b/debian/monkeysphere.docs
new file mode 100644 (file)
index 0000000..4b8144e
--- /dev/null
@@ -0,0 +1,2 @@
+doc/README
+doc/MonkeySpec
diff --git a/debian/monkeysphere.install b/debian/monkeysphere.install
new file mode 100644 (file)
index 0000000..6dd3dda
--- /dev/null
@@ -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 (file)
index 0000000..a8f6c16
--- /dev/null
@@ -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 (executable)
index 0000000..cbe925d
--- /dev/null
@@ -0,0 +1,3 @@
+#!/usr/bin/make -f
+%:
+       dh $@
index 6ee278f20f38fd9e8d562180034599a629313f9f..54aaa72d8bff4c8a6225fe34c227b885205255bf 100644 (file)
@@ -39,9 +39,9 @@ common components
 server-side components
 ----------------------
 * "howler": server gpg maintainer
-  - generates gpg keys for the server
-  - publishes server gpg keys
-  - used to specify userids to trust for user authentication
+  - generate gpg keys for the server
+  - publish server gpg keys
+  - give owner trust to keys for user authentication
 
 * "tamarin": concept - how to trigger or schedule rhesus at admin defined 
        points (e.g. via cron or during ssh connections).
index d8f189754b23dbdf3082c6f2dd34af1e104b6d9f..cda1194336bfb028e8fabd71f46c3d0104fdc088 100644 (file)
@@ -1,36 +1,22 @@
 Monkeysphere README
 ===================
 
-Default files locations (by variable):
-
-MS_HOME=~/.config/monkeysphere
-MS_CONF=$MS_HOME/monkeysphere.conf
-AUTH_HOST_FILE=$MS_HOME/auth_host_ids
-AUTH_USER_FILE=$MS_HOME/auth_user_ids
-GNUPGHOME=~/.gnupg
-STAGING_AREA=$MS_HOME
-
-$STAGING_AREA/host_keys/KEYHASH
-$STAGING_AREA/known_hosts
-$STAGING_AREA/user_keys/KEYHASH
-$STAGING_AREA/authorized_keys
-
 user usage
 ----------
-For a user to update their ms known_hosts file:
+For a user to update their known_hosts file:
 
-$ rhesus --known_hosts
+$ monkeysphere update-known_hosts
 
-For a user to update their ms authorized_keys file:
+For a user to update their monkeysphere authorized_keys file:
 
-$ rhesus --authorized_keys
+$ monkeysphere update-authorized_keys
 
 server service publication
 --------------------------
-To publish a server host key use the "howler" component:
+To publish a server host key:
 
-# howler gen-key
-# howler publish-key
+# monkeysphere-server gen-key
+# monkeysphere-server publish-key
 
 This will generate the key for server with the service URI
 (ssh://server.hostname).  The server admin should now sign the server
@@ -42,38 +28,29 @@ $ gpg --sign-key 'ssh://server.hostname'
 
 server authorized_keys maintenance
 ----------------------------------
-A system can maintain ms authorized_keys files for it's users.  Some
-different variables need to be defined to help manage this.  The way
-this is done is by first defining a new MS_HOME:
+A system can maintain monkeysphere authorized_keys files for it's
+users.
 
-MS_HOME=/etc/monkeysphere
-
-This directory would then have a monkeysphere.conf which defines the
-following variables:
+For each user account on the server, the userids of people authorized
+to log into that account would be placed in:
 
-AUTH_USER_FILE="$MS_HOME"/auth_user_ids/"$USER"
-STAGING_AREA=/var/lib/monkeysphere/stage/$USER
-GNUPGHOME=$MS_HOME/gnupg
+/etc/monkeysphere/authorized_user_ids/USER
 
-For each user account on the server, the userids of people authorized
-to log into that account would be placed in the AUTH_USER_FILE for
-that user.  However, in order for users to become authenticated, the
-server must determine that the user keys have "full" validity.  This
-means that the server must fully trust at least one person whose
-signature on the connecting users key would validate the user.  This
-would generally be the server admin.  If the server admin's keyid is
-XXXXXXXX, then on the server run:
+However, in order for users to become authenticated, the server must
+determine that the user keys have "full" validity.  This means that
+the server must fully trust at least one person whose signature on the
+connecting users key would validate the user.  This would generally be
+the server admin.  If the server admin's keyid is XXXXXXXX, then on
+the server run:
 
-# howler trust-key XXXXXXXX
+# monkeysphere-server trust-keys XXXXXXXX
 
-To update the ms authorized_keys file for user "bob", the system would
-then run the following:
+To update the monkeysphere authorized_keys file for user "bob", the
+system would then run the following:
 
-# USER=bob MS_HOME=/etc/monkeysphere rhesus --authorized_keys
+# monkeysphere-server update-users bob
 
-To update the ms authorized_keys file for all users on the the system:
+To update the monkeysphere authorized_keys file for all users on the
+the system, run the same command with no arguments:
 
-MS_HOME=/etc/monkeysphere
-for USER in $(ls -1 /etc/monkeysphere/auth_user_ids) ; do
-    rhesus --authorized_keys
-done
+# monkeysphere-server update-users
diff --git a/doc/TODO b/doc/TODO
new file mode 100644 (file)
index 0000000..4f32bb0
--- /dev/null
+++ b/doc/TODO
@@ -0,0 +1,124 @@
+Next-Steps Monkeysphere Projects:
+---------------------------------
+
+Detail advantages of monkeysphere: detail the race conditions in ssh,
+   and how the monkeysphere can help you reduce these threat vectors:
+   threat model reduction diagrams.
+
+Determine how openssh handles multiple processes writing to
+   known_hosts/authorized_keys files (lockfile, atomic appends?)
+
+Handle unverified monkeysphere hosts in such a way that they're not
+   always removed from known_hosts file.  Ask user to lsign the host
+   key?
+
+Handle multiple hostnames (multiple user IDs?) when generating host
+   keys with gen-key.
+
+Work out the details (and describe a full use case) for assigning a
+   REVOKER during monkeysphere-server gen_key -- how is this set?  How
+   do we export it so it's available when a second-party revocation is
+   needed?
+
+Actually enable server hostkey publication.
+
+Streamline host key generation, publication, verification.  See
+   doc/george/host-key-publication for what dkg went through on
+   2008-06-19
+
+Ensure that authorized_user_ids are under as tight control as ssh
+   expects from authorized_keys: we don't want monkeysphere to be a
+   weak link in the filesystem.
+
+What happens when a user account has no corresponding
+   /etc/monkeysphere/authorized_user_ids/$USER file?  What gets placed
+   in /var/cache/monkeysphere/authorized_keys/$USER?  It looks
+   currently untouched, which could mean bad things for such a user.
+   - if authorized_user_ids is empty, then the user's authorized_keys
+     file will be also, unless the user-controlled authorized_keys file
+     is added.  I believe this is expected, correct behavior.
+
+Consider the default permissions for
+   /var/cache/monkeysphere/authorized_keys/* (and indeed the whole
+   directory path leading up to that)
+
+As an administrator, how do i reverse the effect of a
+   "monkeysphere-server trust-keys" that i later decide i should not
+   have run?
+
+Make sure alternate ports are handled for known_hosts.
+
+Script to import private key into ssh agent.
+
+Provide a friendly interactive UI for marginal or failing client-side
+   hostkey verifications.  Handle the common cases smoothly, and
+   provide good debugging info for the unusual cases.
+
+Make sure onak properly escapes user IDs with colons in them.
+
+Build a decent, presentable web site for documentation, evangelism,
+   etc.  Include a mention of how to report trouble or concerns.
+
+Create ssh2openpgp or convert to full-fledged keytrans.
+
+Resolve the bugs listed in openpgp2ssh(1):BUGS.
+
+Understand and document alternate trustdb models.
+
+Understand and document the output of gpg --check-trustdb:
+ gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
+ gpg: depth: 0  valid:   2  signed:  20  trust: 0-, 0q, 0n, 0m, 0f, 2u
+ gpg: depth: 1  valid:  20  signed:  67  trust: 15-, 0q, 1n, 3m, 1f, 0u
+ gpg: next trustdb check due at 2008-10-09
+
+Understand and document the numeric values between sig! and the keyid
+   in "gpg --check-sigs $KEYID" .  Compare with the details found from
+   "gpg --with-colons --check-sigs $KEYID".  This has to do with trust
+   signatures.
+
+Fix gpg's documentation to clarify the difference between validity and
+   ownertrust.  Include better documentation for trust signatures.
+
+Make it easier to do domain-relative ssh host trust signatures with
+   gnupg. (e.g. "i trust Jamie McClelland (keyID 76CC057D) to properly
+   identify ssh servers in the mayfirst.org domain") See:
+   http://tools.ietf.org/html/rfc4880#section-5.2.3.21 and grep for
+   "tsign" in gpg(1).
+
+Fix the order of questions when user does a tsign in gpg or gpg2.
+
+File bug against ssh-keygen about how "-R" option removes comments
+   from known_hosts file.
+
+File bug against ssh-keygen to see if we can get it to write to hash a
+   known_hosts file to/from stdout/stdin.
+
+Add environment variables sections to man pages.
+
+Environment variable scoping.
+
+Move environment variable precedence before conf file.
+
+When using ssh-proxycommand, if only host keys found are expired or
+   revoked, then output loud warning with prompt, or fail hard.
+
+Update monkeysphere-ssh-proxycommand man page with new keyserver
+   checking policy info.
+
+Update monkeysphere-ssh-proxycommand man page with info about
+   no-connect option.
+
+File bug against seahorse about how, when creating new primary keys,
+   it presents option for "RSA (sign only)" but then creates an "esca"
+   key.
+
+File bug against enigmail about lack of ability to create subkeys.
+
+Priviledge separation: monkeysphere user to handle authn keyring and
+   generate authorized_keys file (which would be moved into place by
+   root).  Host keyring would be owned by root.
+
+Check permissions of authorized_user_ids file to be writable only by
+   user and root (same as authorized_keys)
+
+Improve function that sets owner trust for keys in server keychain.
diff --git a/doc/george/changelog b/doc/george/changelog
new file mode 100644 (file)
index 0000000..aefbf7f
--- /dev/null
@@ -0,0 +1,83 @@
+******************************************************************************
+*                                                                            *
+*                      george system log                                    *
+*                                                                            *
+******************************************************************************
+*  Please add new entries in reverse chronological order whenever you make   *
+*  changes to this system                                                   *
+******************************************************************************
+
+2008-06-22 - dkg
+       * installed screen (mjgoins and i were collaborating)
+       
+2008-06-21 - micah
+       * Restored /etc/init.d/ssh to original package state and changed
+       /etc/default/ssh to have 'unset SSHD_OOM_ADJUST' instead.
+       
+2008-06-20 - micah
+       * Commented out the 'export SSHD_OOM_ADJUST=-17' from the
+       /etc/init.d/ssh initscript, and the 'SSHD_OOM_ADJUST=-17' from
+       /etc/default/ssh in order to make this error go away:
+       "error writing /proc/self/oom_adj: Operation not permitted"
+       (c.f. Debian #487325)
+         
+2008-06-20 - dkg
+       * touched /etc/environment to get rid of some spurious auth.log
+       entries.
+       * turned up sshd's LogLevel from INFO to DEBUG
+       
+2008-06-19 - dkg
+       * installed rsync (for maintaining a public apt repo)
+       
+       * configured mathopd to listen on port 80, serving /srv/www as /
+       and /srv/apt as /debian.  We've got nothing in /srv/www at the
+       moment, though.
+
+       * installed lsof and psmisc as sysadmin utilities.  sorry for the
+       bloat!
+
+       * installed strace to try to figure out why onak is segfaulting.
+       
+2008-06-19 - dkg
+       * removed etch sources, switched "testing" to "lenny", added
+       lenny/updates, removed all contrib and non-free.
+       
+       * removed testing pin in /etc/apt/preferences
+       * ran the upgrade
+       
+       * reset emacs22 to emacs22-nox (avoiding dependencies)
+       
+       * removed sysklog and klogd because of errors restarting klogd.
+       Installed syslog-ng in their stead, which still gives errors
+       related to /proc/kmsg unreadability, but the install completes :/
+       
+       * added experimental
+       * juggled pinning: experimental: 1, unstable: 2 
+       * added mathopd onak, tweaked /etc/mathopd.conf and /etc/onak.conf
+       
+       * installed monkeysphere v0.1-1, changed host key, published
+       them via the local keyserver (see host-key-publication)
+
+       * added local unprivileged user accounts for everyone listed in
+       /usr/share/doc/monkeysphere/copyright
+
+       * configured authorized_user_ids for every user account based on
+       my best guess at their OpenPGP User ID (see
+       user-id-configuration).
+
+       * set up a cronjob (in /etc/crontab) to run "monkeysphere-server
+       update-users" at 26 minutes past the hour.
+       
+2008-06-18 - jrollins
+       * installed less, emacs;
+       * aptitude update && aptitude dist-upgrade
+
+2008-06-18 - micah
+       * debootstrap'd debian etch install
+       * installed /etc/apt/sources.list with local proxy sources for etch,
+         testing, unstable, backports and volatile
+       * configured /etc/apt/preferences and apt.conf.d/local-conf to
+         pin etch, but make testing, sid and backports available
+       * added backports.org apt-key
+       * installed openssh-server and openssh-client packages
+       * added dkg, jrollins, mjgoins ssh public_keys to /root/.ssh/authorized_keys
diff --git a/doc/george/host-key-publication b/doc/george/host-key-publication
new file mode 100644 (file)
index 0000000..03e2510
--- /dev/null
@@ -0,0 +1,28 @@
+2008-06-19 02:34:57-0400
+------------------------
+
+Adding george's host key to the monkeysphere was more complicated than
+it needed to be.
+
+As the server admin, i did (accepting the defaults where possible):
+
+ monkeysphere-server gen-key
+ KEYID=$(GNUPGHOME=/etc/monkeysphere/gnupg gpg --with-colons --list-key  =ssh://$(hostname --fqdn) | grep ^pub: | cut -f5 -d:)
+ (umask 077 && GNUPGHOME=/etc/monkeysphere/gnupg gpg --export-secret-key $KEYID | openpgp2ssh $KEYID >/etc/monkeysphere/ssh_host_rsa_key)
+   # modify /etc/ssh/sshd_config to remove old host keys lines, and 
+   # add new line: HostKey /etc/monkeysphere/ssh_host_rsa_key 
+ /etc/init.d/ssh restart
+
+ KEYSERVER=george.riseup.net monkeysphere-server publish-key
+  # (needed to publish by hand here because of reasonable sanity checks)
+ monkeysphere-server show-fingerprint
+
+  # then from a remote host:
+ gpg --keyserver george.riseup.net --search =ssh://george.riseup.net
+ gpg --fingerprint --sign-key =ssh://george.riseup.net
+ KEYID=$(gpg --with-colons --list-key =ssh://george.riseup.net | grep ^pub: | cut -f5 -d:)
+ gpg --keyserver george.riseup.net --send "$KEYID"
+ gpg --keyserver george.riseup.net --send "$MYGPGID"
+
+
+How could this have been streamlined?
diff --git a/doc/george/policy b/doc/george/policy
new file mode 100644 (file)
index 0000000..a17a310
--- /dev/null
@@ -0,0 +1,33 @@
+Policy for maintaining george.riseup.net
+----------------------------------------
+
+Riseup graciously provided the MonkeySphere project with a vserver for
+testing and public documentation.  This is known as george.riseup.net,
+for those who are curious about the MonkeySphere.
+
+george will be maintained as a debian lenny machine, with minimal
+packages from experimental as needed for installing and running what
+we build elsewhere.
+
+george will host 3 public-facing services: an ssh daemon on port 22,
+an http service on port 80, and an OpenPGP keyserver (the HKP
+protocol) on port 11371.
+
+Administration of george is a shared responsibility across the core
+members of the MonkeySphere development team.  Administrators will log
+changes in their git repositories, in doc/george/changelog (a peer of
+this policy file).
+
+monkeysphere packages installed on george will use unique, tagged
+version numbers so we know what we're running.
+
+We will try to keep the installation as minimal as possible while
+still allowing for comfortable day-to-day administration.
+
+We will use aptitude for package management where possible.
+
+Outstanding questions:
+
+Who should have superuser access?
+
+Who should get regular user accounts?
diff --git a/doc/george/user-id-configuration b/doc/george/user-id-configuration
new file mode 100644 (file)
index 0000000..9a7f4d2
--- /dev/null
@@ -0,0 +1,40 @@
+2008-06-19 03:00:58-0400
+------------------------
+
+setting up authorized_user_id configuration on george was also more
+cumbersome than it needs to be.  Here's what i (dkg) did:
+
+monkeysphere-server trust-keys 0EE5BE979282D80B9F7540F1CCD2ED94D21739E9
+
+monkeysphere-server update-user-userids dkg 'Daniel Kahn Gillmor <dkg@fifthhorseman.net>'
+monkeysphere-server update-user-userids jrollins 'Jameson Rollins <jrollins@fifthhorseman.net>'
+monkeysphere-server update-user-userids micah 'Micah Anderson <micah@riseup.net>' 
+monkeysphere-server update-user-userids mjgoins 'Matthew Goins <mjgoins@openflows.com>'
+monkeysphere-server update-user-userids ross 'Ross Glover <ross@ross.mayfirst.org>'
+monkeysphere-server update-user-userids jamie 'Jamie McClelland <jamie@mayfirst.org>'
+monkeysphere-server update-user-userids mlcastle 'mike castleman <m@mlcastle.net>'
+monkeysphere-server update-user-userids enw 'Elliot Winard <enw@caveteen.com>'
+monkeysphere-server update-user-userids greg 'Greg Lyle <greg@stealthisemail.com>' 
+
+
+then i added a scheduled:
+
+  monkeysphere-server update-users
+
+to run hourly via /etc/crontab
+
+and made sure that root's keys were working with a temporary symlink
+(see TODO about that business)
+
+and then modified /etc/ssh/sshd_config with:
+
+ AuthorizedKeysFile      /var/cache/monkeysphere/authorized_keys/%u
+
+
+Some outstanding questions:
+
+ * Should we ship a scheduled monkeysphere-server update-users cron
+   job automatically?
+
+ * why was i not prompted to confirm the trust-keys line, which seems
+   like the most delicate/sensitive line of all of them?
diff --git a/etc/monkeysphere-server.conf b/etc/monkeysphere-server.conf
new file mode 100644 (file)
index 0000000..847e879
--- /dev/null
@@ -0,0 +1,31 @@
+# MonkeySphere server configuration file.
+
+# This is an sh-style shell configuration file.  Variable names should
+# be separated from their assignements by a single '=' and no spaces.
+
+# GPG home directory for server
+#GNUPGHOME=/etc/monkeysphere/gnupg
+
+# GPG keyserver to search for keys
+#KEYSERVER=subkeys.pgp.net
+
+# Required user key capabilities
+# Must be quoted, lowercase, space-seperated list of the following:
+#   e = encrypt
+#   s = sign
+#   c = certify
+#   a = authentication
+#REQUIRED_USER_KEY_CAPABILITY="a"
+
+# Path to authorized_user_ids file to process to create
+# authorized_keys file.  '%h' will be replaced by the home directory
+# of the user, and %u will be replaced by the username of the user.
+# For purely admin-controlled authorized_user_ids, you might put them
+# in /etc/monkeysphere/authorized_user_ids/%u
+#AUTHORIZED_USER_IDS="%h/.config/monkeysphere/authorized_user_ids"
+
+# Whether to add user controlled authorized_keys file to
+# monkeysphere-generated authorized_keys file.  Should be path to file
+# where '%h' will be replaced by the home directory of the user.
+# To not add any user-controlled file, put "-"
+#USER_CONTROLLED_AUTHORIZED_KEYS="%h/.ssh/authorized_keys"
diff --git a/etc/monkeysphere.conf b/etc/monkeysphere.conf
new file mode 100644 (file)
index 0000000..f2ba4a7
--- /dev/null
@@ -0,0 +1,33 @@
+# MonkeySphere system-wide client configuration file.
+
+# This is an sh-style shell configuration file.  Variable names should
+# be separated from their assignements by a single '=' and no spaces.
+
+# GPG home directory
+#GNUPGHOME=~/.gnupg
+
+# GPG keyserver to search for keys
+#KEYSERVER=subkeys.pgp.net
+
+# Required key capabilities
+# Must be quoted, lowercase, space-seperated list of the following:
+#   e = encrypt
+#   s = sign
+#   c = certify
+#   a = authentication
+#REQUIRED_HOST_KEY_CAPABILITY="a"
+#REQUIRED_USER_KEY_CAPABILITY="a"
+
+# ssh known_hosts file
+#KNOWN_HOSTS=~/.ssh/known_hosts
+
+# Whether or not to hash the generated known_hosts lines.
+# Should be "true" or "false"
+#HASH_KNOWN_HOSTS=true
+
+# ssh authorized_keys file
+#AUTHORIZED_KEYS=~/.ssh/known_hosts
+
+# This overrides other environment variables
+# NOTE: there is leakage
+#CHECK_KEYRING=true
diff --git a/gpg2ssh/Makefile b/gpg2ssh/Makefile
deleted file mode 100644 (file)
index 1e816e2..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-all: monkeysphere gpg2ssh ssh2gpg
-
-monkeysphere: main.c gnutls-helpers.o
-       gcc -g -Wall --pedantic -o monkeysphere main.c `libgnutls-config --libs --cflags` -lgnutls-extra gnutls-helpers.o
-
-gpg2ssh: gpg2ssh.c gnutls-helpers.o
-       gcc -g -Wall --pedantic -o gpg2ssh gpg2ssh.c `libgnutls-config --libs --cflags` -lgnutls-extra gnutls-helpers.o
-
-ssh2gpg: ssh2gpg.c gnutls-helpers.o
-       gcc -g -Wall --pedantic -o ssh2gpg ssh2gpg.c `libgnutls-config --libs --cflags` -lgnutls-extra gnutls-helpers.o
-
-%.o: %.c
-       gcc -g -Wall --pedantic -o $@ -c $<
-
-clean: 
-       rm -f monkeysphere *.o gpg2ssh ssh2gpg
-
-.PHONY: clean
diff --git a/gpg2ssh/gnutls-helpers.c b/gpg2ssh/gnutls-helpers.c
deleted file mode 100644 (file)
index 6eae29e..0000000
+++ /dev/null
@@ -1,364 +0,0 @@
-/* Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net> */
-/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */
-/* License: GPL v3 or later */
-
-#include "gnutls-helpers.h"
-/* for htonl() */
-#include <arpa/inet.h>
-
-/* for setlocale() */
-#include <locale.h>
-
-/* for isalnum() */
-#include <ctype.h>
-
-int loglevel = 0;
-
-
-void err(const char* fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-  vfprintf(stderr, fmt, ap);
-  va_end(ap);
-  fflush(stderr);
-}
-
-void logfunc(int level, const char* string) {
-  fprintf(stderr, "GnuTLS Logging (%d): %s\n", level, string);
-}
-
-void init_keyid(gnutls_openpgp_keyid_t keyid) {
-  memset(keyid, 'x', sizeof(gnutls_openpgp_keyid_t));
-}
-
-
-
-void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid)
-{
-  static const char hex[16] = "0123456789ABCDEF";
-  unsigned int kix = 0, outix = 0;
-  
-  while (kix < sizeof(gnutls_openpgp_keyid_t)) {
-    out[outix] = hex[(keyid[kix] >> 4) & 0x0f];
-    out[outix + 1] = hex[keyid[kix] & 0x0f];
-    kix++;
-    outix += 2;
-  }
-}
-
-
-int init_gnutls() {
-  const char* version = NULL;
-  const char* debug_string = NULL;
-  int ret;
-
-  if (ret = gnutls_global_init(), ret) {
-    err("Failed to do gnutls_global_init() (error: %d)\n", ret);
-    return 1;
-  }
-
-  version = gnutls_check_version(NULL);
-
-  if (version) 
-    err("gnutls version: %s\n", version);
-  else {
-    err("no version found!\n");
-    return 1;
-  }
-
-  if (debug_string = getenv("MONKEYSPHERE_DEBUG"), debug_string) {
-    loglevel = atoi(debug_string);
-    gnutls_global_set_log_function(logfunc);
-    
-    gnutls_global_set_log_level(loglevel);
-    err("set log level to %d\n", loglevel);
-  }
-  return 0;
-}
-
-void init_datum(gnutls_datum_t* d) {
-  d->data = NULL;
-  d->size = 0;
-}
-void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src) {
-  dest->data = gnutls_realloc(dest->data, src->size);
-  dest->size = src->size;
-  memcpy(dest->data, src->data, src->size);
-}
-int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b) {
-  if (a->size > b->size) {
-    err("a is larger\n");
-    return 1;
-  }
-  if (a->size < b->size) {
-    err("b is larger\n");
-    return -1;
-  }
-  return memcmp(a->data, b->data, a->size);
-}
-void free_datum(gnutls_datum_t* d) {
-  gnutls_free(d->data);
-  d->data = NULL;
-  d->size = 0;
-}
-
-/* read the passed-in string, store in a single datum */
-int set_datum_string(gnutls_datum_t* d, const char* s) {
-  unsigned int x = strlen(s)+1;
-  unsigned char* c = NULL;
-
-  c = gnutls_realloc(d->data, x);
-  if (NULL == c)
-    return -1;
-  d->data = c;
-  d->size = x;
-  memcpy(d->data, s, x);
-  return 0;
-}
-
-/* read the passed-in file descriptor until EOF, store in a single
-   datum */
-int set_datum_fd(gnutls_datum_t* d, int fd) {
-  unsigned int bufsize = 1024;
-  unsigned int len = 0;
-
-  FILE* f = fdopen(fd, "r");
-  if (bufsize > d->size) {
-    bufsize = 1024;
-    d->data = gnutls_realloc(d->data, bufsize);
-    if (d->data == NULL) {
-      err("out of memory!\n");
-      return -1;
-    }
-    d->size = bufsize;
-  } else {
-    bufsize = d->size;
-  }
-  f = fdopen(fd, "r");
-  if (NULL == f) {
-    err("could not fdopen FD %d\n", fd);
-  }
-  clearerr(f);
-  while (!feof(f) && !ferror(f)) { 
-    if (len == bufsize) {
-      /* allocate more space by doubling: */
-      bufsize *= 2;
-      d->data = gnutls_realloc(d->data, bufsize);
-      if (d->data == NULL) {
-       err("out of memory!\n"); 
-       return -1;
-      };
-      d->size = bufsize;
-    }
-    len += fread(d->data + len, 1, bufsize - len, f);
-    /*     err("read %d bytes\n", len); */
-  }
-  if (ferror(f)) {
-    err("Error reading from fd %d (error: %d) (error: %d '%s')\n", fd, ferror(f), errno, strerror(errno));
-    return -1;
-  }
-    
-  /* touch up buffer size to match reality: */
-  d->data = gnutls_realloc(d->data, len);
-  d->size = len;
-  return 0;
-}
-
-/* read the file indicated (by name) in the fname parameter.  store
-   its entire contents in a single datum. */
-int set_datum_file(gnutls_datum_t* d, const char* fname) {
-  struct stat sbuf;
-  unsigned char* c = NULL;
-  FILE* file = NULL;
-  size_t x = 0;
-
-  if (0 != stat(fname, &sbuf)) {
-    err("failed to stat '%s'\n", fname);
-    return -1;
-  }
-  
-  c = gnutls_realloc(d->data, sbuf.st_size);
-  if (NULL == c) {
-    err("failed to allocate %d bytes for '%s'\n", sbuf.st_size, fname);
-    return -1;
-  }
-
-  d->data = c;
-  d->size = sbuf.st_size;
-  file = fopen(fname, "r");
-  if (NULL == file) {
-    err("failed to open '%s' for reading\n",  fname);
-    return -1;
-  }
-
-  x = fread(d->data, d->size, 1, file);
-  if (x != 1) {
-    err("tried to read %d bytes, read %d instead from '%s'\n", d->size, x, fname);
-    fclose(file);
-    return -1;
-  }
-  fclose(file);
-  return 0;
-}
-
-int write_datum_fd(int fd, const gnutls_datum_t* d) {
-  if (d->size != write(fd, d->data, d->size)) {
-    err("failed to write body of datum.\n");
-    return -1;
-  }
-  return 0;
-}
-
-
-int write_datum_fd_with_length(int fd, const gnutls_datum_t* d) {
-  uint32_t len;
-  int looks_negative = (d->data[0] & 0x80);
-  unsigned char zero = 0;
-
-  /* if the first bit is 1, then the datum will appear negative in the
-     MPI encoding style used by OpenSSH.  In that case, we'll increase
-     the length by one, and dump out one more byte */
-
-  if (looks_negative) {
-    len = htonl(d->size + 1);
-  } else {
-    len = htonl(d->size);
-  }
-  if (write(fd, &len, sizeof(len)) != sizeof(len)) {
-    err("failed to write size of datum.\n");
-    return -2;
-  }
-  if (looks_negative) {
-    if (write(fd, &zero, 1) != 1) {
-      err("failed to write padding byte for MPI.\n");
-      return -2;
-    }
-  }
-  return write_datum_fd(fd, d);
-}
-
-int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num) {
-  unsigned int i;
-  int ret;
-
-  for (i = 0; i < num; i++)
-    if (ret = write_datum_fd_with_length(fd, d[i]), ret != 0)
-      return ret;
-
-  return 0;
-}
-
-
-int datum_from_string(gnutls_datum_t* d, const char* str) {
-  d->size = strlen(str);
-  d->data = gnutls_realloc(d->data, d->size);
-  if (d->data == 0)
-    return ENOMEM;
-  memcpy(d->data, str, d->size);
-  return 0;
-}
-
-
-int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]) {
-  int p[2];
-  int ret;
-
-  if (pid == NULL) {
-    err("bad pointer passed to create_writing_pipe()\n");
-    return -1;
-  }
-
-  if (ret = pipe(p), ret == -1) {
-    err("failed to create a pipe (error: %d \"%s\")\n", errno, strerror(errno));
-    return -1;
-  }
-
-  *pid = fork();
-  if (*pid == -1) {
-    err("Failed to fork (error: %d \"%s\")\n", errno, strerror(errno));
-    return -1;
-  }
-  if (*pid == 0) { /* this is the child */
-    close(p[1]); /* close unused write end */
-    
-    if (0 != dup2(p[0], 0)) { /* map the reading end into stdin */
-      err("Failed to transfer reading file descriptor to stdin (error: %d \"%s\")\n", errno, strerror(errno));
-      exit(1);
-    }
-    execv(path, argv);
-    err("exec %s failed (error: %d \"%s\")\n", path, errno, strerror(errno));
-    /* close the open file descriptors */
-    close(p[0]);
-    close(0);
-
-    exit(1);
-  } else { /* this is the parent */
-    close(p[0]); /* close unused read end */
-    return p[1];
-  }
-}
-
-int validate_ssh_host_userid(const char* userid) {
-  char* oldlocale = setlocale(LC_ALL, "C");
-  
-  /* choke if userid does not match the expected format
-     ("ssh://fully.qualified.domain.name") */
-  if (strncmp("ssh://", userid, strlen("ssh://")) != 0) {
-    err("The user ID should start with ssh:// for a host key\n");
-    goto fail;
-  }
-  /* so that isalnum will work properly */
-  userid += strlen("ssh://");
-  while (0 != (*userid)) {
-    if (!isalnum(*userid)) {
-      err("label did not start with a letter or a digit! (%s)\n", userid);
-      goto fail;
-    }
-    userid++;
-    while (isalnum(*userid) || ('-' == (*userid)))
-      userid++;
-    if (('.' == (*userid)) || (0 == (*userid))) { /* clean end of label:
-                                                check last char
-                                                isalnum */
-      if (!isalnum(*(userid - 1))) {
-       err("label did not end with a letter or a digit!\n");
-       goto fail;
-      }
-      if ('.' == (*userid)) /* advance to the start of the next label */
-       userid++;
-    } else {
-      err("invalid character in domain name: %c\n", *userid);
-      goto fail;
-    }
-  }
-  /* ensure that the last character is valid: */
-  if (!isalnum(*(userid - 1))) {
-    err("hostname did not end with a letter or a digit!\n");
-    goto fail;
-  }
-  /* FIXME: fqdn's can be unicode now, thanks to RFC 3490 -- how do we
-     make sure that we've got an OK string? */
-
-  return 0;
-
- fail:
-  setlocale(LC_ALL, oldlocale);
-  return 1;
-}
-
-/* http://tools.ietf.org/html/rfc4880#section-5.5.2 */
-size_t get_openpgp_mpi_size(gnutls_datum_t* d) {
-  return 2 + d->size;
-}
-
-int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d) {
-  uint16_t x;
-
-  x = d->size * 8;
-  x = htons(x);
-  
-  write(fd, &x, sizeof(x));
-  write(fd, d->data, d->size);
-  
-  return 0;
-}
diff --git a/gpg2ssh/gnutls-helpers.h b/gpg2ssh/gnutls-helpers.h
deleted file mode 100644 (file)
index 9ea22a3..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/* Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net> */
-/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */
-/* License: GPL v3 or later */
-
-
-#include <gnutls/gnutls.h>
-#include <gnutls/openpgp.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <stdarg.h>
-
-/* Functions to help dealing with GnuTLS for monkeysphere key
-   translation projects: */
-
-/* set everything up, including logging levels.  Return 0 on
-   success */
-int init_gnutls();
-
-/* logging and output functions: */
-
-void err(const char* fmt, ...);
-void logfunc(int level, const char* string);
-
-/* basic datum manipulations: */
-
-void init_datum(gnutls_datum_t* d);
-void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src);
-int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b);
-void free_datum(gnutls_datum_t* d);
-int write_datum_fd(int fd, const gnutls_datum_t* d);
-int write_datum_fd_with_length(int fd, const gnutls_datum_t* d);
-int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num);
-
-/* set up a datum from a null-terminated string */
-int datum_from_string(gnutls_datum_t* d, const char* str);
-
-/* keyid manipulations: */
-typedef unsigned char printable_keyid[16];
-
-void init_keyid(gnutls_openpgp_keyid_t keyid);
-void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid);
-
-/* functions to get data into datum objects: */
-
-/* read the passed-in string, store in a single datum */
-int set_datum_string(gnutls_datum_t* d, const char* s);
-
-/* read the passed-in file descriptor until EOF, store in a single
-   datum */
-int set_datum_fd(gnutls_datum_t* d, int fd);
-
-/* read the file indicated (by name) in the fname parameter.  store
-   its entire contents in a single datum. */
-int set_datum_file(gnutls_datum_t* d, const char* fname);
-
-/* set up file descriptor pipe for writing (child process pid gets
-   stored in pid, fd is returned)*/
-int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]);
-
-/* return 0 if userid matches the monkeysphere spec for ssh host user IDs */
-int validate_ssh_host_userid(const char* userid);
-
-/* how many bytes will it take to write out this datum in OpenPGP MPI form? */
-size_t get_openpgp_mpi_size(gnutls_datum_t* d);
-
-/* write the MPI stored in gnutls_datum_t to file descriptor fd: */
-int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d);
diff --git a/gpg2ssh/gpg2ssh.c b/gpg2ssh/gpg2ssh.c
deleted file mode 100644 (file)
index a1e94df..0000000
+++ /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 (file)
index d6bac68..0000000
+++ /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 (file)
index b14a540..0000000
+++ /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 (executable)
index 0b67c02..0000000
+++ /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 (file)
index ee60701..0000000
+++ /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 (file)
index 0000000..c4196f2
--- /dev/null
@@ -0,0 +1,53 @@
+.TH MONKEYSPHERE-SSH-PROXYCOMMAND "1" "June 2008" "monkeysphere 0.1" "User Commands"
+
+.SH NAME
+
+monkeysphere-ssh-proxycommand \- MonkeySphere ssh ProxyCommand script
+
+.SH DESCRIPTION
+
+\fBmonkeysphere-ssh-proxy\fP is an ssh proxy command that can be used
+to trigger a monkeysphere update of the known_hosts file for the hosts
+that are being connected to.  It is meant to be run as an ssh
+ProxyCommand.  This can either be done by specifying the proxy command
+on the command line:
+
+.B ssh -o ProxyCommand="monkeysphere-ssh-proxycommand %h %p" ...
+
+or by adding the following line to your ~/.ssh/config script:
+
+.B ProxyCommand monkeysphere-ssh-proxycommand %h %p
+
+The script can easily be incorporated into other ProxyCommand scripts
+by calling it with the "--no-connect" option, ie:
+
+.B monkeysphere-ssh-proxycommand --no-connect "$HOST" "$PORT"
+
+This will run everything but will not exec netcat to make the tcp
+connection to the host.
+
+.SH KEYSERVER CHECKING
+
+The proxy command has a fairly nuanced policy for when keyservers are
+queried when processing host.  If the host userID is not found in
+either the user's keyring or in the known_hosts file, then the
+keyserver is queried for the host userID.  If the host userID is found
+in the user's keyring, then the keyserver is not checked.  This is
+because...  If the host userID is not found in the user's keyring, but
+the host is listed in the known_hosts file, then defered check is
+scheduled.
+
+.SH ENVIRONMENT VARIABLES
+
+.TP
+KEYSERVER The keyserver to query.
+
+.SH AUTHOR
+
+Written by Jameson Rollins <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 (file)
index 0000000..30e35bb
--- /dev/null
@@ -0,0 +1,94 @@
+.TH MONKEYSPHERE "1" "June 2008" "monkeysphere 0.1" "User Commands"
+
+.SH NAME
+
+monkeysphere \- MonkeySphere client user interface
+
+.SH SYNOPSIS
+
+.B monkeysphere \fIcommand\fP [\fIargs\fP]
+
+.SH DESCRIPTION
+
+MonkeySphere is a system to leverage the OpenPGP Web of Trust for ssh
+authentication and encryption.  OpenPGP keys are tracked via GnuPG,
+and added to the ssh authorized_keys and known_hosts files to be used
+for authentication of ssh connections.
+
+\fBmonkeysphere\fP is the MonkeySphere client utility.
+
+.SH SUBCOMMANDS
+
+\fBmonkeysphere\fP takes various subcommands:
+.TP
+.B update-known_hosts [HOST]...
+Update the known_hosts file.  For each specified host, gpg will be
+queried for a key associated with the host URI (see HOST URIs),
+querying a keyserver if specified.  If a key is found, it will be
+converted to an ssh key, and any matching ssh keys will be removed
+from the user's known_hosts file.  If the found key is acceptable (see
+KEY ACCEPTABILITY), then the key will be updated and re-added to the
+known_hosts file.  If no gpg key is found for the host, then nothing
+is done.  If no hosts are specified, all hosts listed in the
+known_hosts file will be processed.  `k' may be used in place of
+`update-known_hosts'.
+.TP
+.B update-authorized_keys
+Update the monkeysphere authorized_keys file.  For each user ID in the
+user's authorized_user_ids file, gpg will be queried for keys
+associated with that user ID, querying a keyserver if specified.  If a
+key is found, it will be converted to an ssh key, and any matching ssh
+keys will be removed from the user's authorized_keys file.  If the
+found key is acceptable (see KEY ACCEPTABILITY), then the key will be
+updated and re-added to the authorized_keys file.  If no gpg key is
+found for the user ID, then nothing is done.  `a' may be used in place
+of `update-authorized_keys'.
+.TP
+.B gen-subkey KEYID
+Generate an `a` capable subkey.  For the primary key with the
+specified key ID, generate a subkey with "authentication" capability
+that can be used for MonkeySphere transactions.  `g' may be used in
+place of `gen-subkey'.
+.TP
+.B help
+Output a brief usage summary.  `h' or `?' may be used in place of
+`help'.
+
+.SH HOST URIs
+
+Host OpenPGP keys have associated user IDs that use the ssh URI
+specification for the host, ie. "ssh://host.full.domain".
+
+.SH KEY ACCEPTABILITY
+
+GPG keys are considered acceptable if the following criteria are met:
+.TP
+.B capability
+The key must have the "authentication" ("a") usage flag set.
+.TP
+.B validity
+The key must be "fully" valid, and must not be expired or revoked.
+
+.SH FILES
+
+.TP
+~/.config/monkeysphere/monkeysphere.conf
+User monkeysphere config file.
+.TP
+/etc/monkeysphere/monkeysphere.conf
+System-wide monkeysphere config file.
+.TP
+~/.config/monkeysphere/authorized_user_ids
+OpenPGP user IDs associated with keys that will be checked for
+addition to the authorized_keys file.
+
+.SH AUTHOR
+
+Written by Jameson Rollins <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 (file)
index 0000000..bea1da5
--- /dev/null
@@ -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 (file)
index 0000000..2b5af5e
--- /dev/null
@@ -0,0 +1,102 @@
+.TH MONKEYSPHERE-SERVER "1" "June 2008" "monkeysphere 0.1" "User Commands"
+
+.SH NAME
+
+monkeysphere-server \- monkeysphere server admin user interface
+
+.SH SYNOPSIS
+
+.B monkeysphere-server \fIcommand\fP [\fIargs\fP]
+
+.SH DESCRIPTION
+
+\fBMonkeySphere\fP is a system to leverage the OpenPGP Web of Trust
+for ssh authentication and encryption.  OpenPGP keys are tracked via
+GnuPG, and added to the ssh authorized_keys and known_hosts files to
+be used for authentication of ssh connections.
+
+\fBmonkeysphere-server\fP is the MonkeySphere server admin utility.
+
+.SH SUBCOMMANDS
+
+\fBmonkeysphere-server\fP takes various subcommands:
+.TP
+.B update-users [USER]...
+Update the admin-controlled authorized_keys files for user.  For each
+user specified, user ID's listed in the user's authorized_user_ids
+file are processed, and the user's authorized_keys file in
+/var/cache/monkeysphere/authorized_keys/USER.  See `man monkeysphere'
+for more info.  If the USER_CONTROLLED_AUTHORIZED_KEYS variable is
+set, then a user-controlled authorized_keys file (usually
+~USER/.ssh/authorized_keys) is added to the authorized_keys file.  `k'
+may be used in place of `update-known_hosts'.
+.TP
+.B gen-key
+Generate a gpg key for the host.  `g' may be used in place of
+`gen-key'.
+.TP
+.B show-fingerprint
+Show the fingerprint for the host's OpenPGP key.  `f' may be used in place of
+`show-fingerprint'.
+.TP
+.B publish-key
+Publish the host's gpg key to the keyserver.  `p' may be used in place
+of `publish-key'.
+.TP
+.B trust-keys KEYID...
+Mark key specified with key IDs with full owner trust.  `t' may be used
+in place of `trust-keys'.
+.TP
+.B help
+Output a brief usage summary.  `h' or `?' may be used in place of
+`help'.
+
+.SH SETUP
+
+In order to start using the monkeysphere, there are a couple of things
+you need to do first.  The first is to generate an OpenPGP key for the
+server and convert that key to an ssh key that can be used by ssh for
+host authentication.  To do this, run the "gen-key" subcommand.  Once
+that is done, publish the key to a keyserver with "publish-key"
+subcommand.  Finally, you need to modify the sshd_config to tell sshd
+where the new server host key:
+
+HostKey /etc/monkeysphere/ssh_host_rsa_key
+
+If the server will also handle user authentication through
+monkeysphere-generated authorized_keys files, set the following:
+
+AuthorizedKeysFile /var/cache/monkeysphere/authorized_keys/%u
+
+Once those changes are made, restart the ssh server.
+
+.SH FILES
+
+.TP
+/etc/monkeysphere/monkeysphere-server.conf
+System monkeysphere-server config file.
+.TP
+/etc/monkeysphere/monkeysphere.conf
+System-wide monkeysphere config file.
+.TP
+/etc/monkeysphere/gnupg
+Monkeysphere GNUPG home directory.
+.TP
+/etc/monkeysphere/ssh_host_rsa_key
+Copy of the host's private key in ssh format, suitable for use by sshd.
+.TP
+/etc/monkeysphere/authorized_user_ids/USER
+Server maintained authorized_user_ids files for users.
+.TP
+/var/cache/monkeysphere/authorized_keys/USER
+User authorized_keys file.
+
+.SH AUTHOR
+
+Written by Jameson Rollins <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 (file)
index cd5e3b2..0000000
+++ /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 (file)
index 4d383d5..0000000
+++ /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 (executable)
index 7a43fca..0000000
+++ /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 (file)
index 0000000..b220150
--- /dev/null
@@ -0,0 +1,571 @@
+# -*-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
+ERR=0
+export ERR
+
+########################################################################
+### UTILITY FUNCTIONS
+
+error() {
+    log "$1"
+    ERR=${2:-'1'}
+}
+
+failure() {
+    echo "$1" >&2
+    exit ${2:-'1'}
+}
+
+# write output to stderr
+log() {
+    echo -n "ms: " >&2
+    echo "$@" >&2
+}
+
+loge() {
+    echo "$@" >&2
+}
+
+# cut out all comments(#) and blank lines from standard input
+meat() {
+    grep -v -e "^[[:space:]]*#" -e '^$'
+}
+
+# cut a specified line from standard input
+cutline() {
+    head --line="$1" | tail -1
+}
+
+# check that characters are in a string (in an AND fashion).
+# used for checking key capability
+# check_capability capability a [b...]
+check_capability() {
+    local usage
+    local capcheck
+
+    usage="$1"
+    shift 1
+
+    for capcheck ; do
+       if echo "$usage" | grep -q -v "$capcheck" ; then
+           return 1
+       fi
+    done
+    return 0
+}
+
+# convert escaped characters from gpg output back into original
+# character
+# FIXME: undo all escape character translation in with-colons gpg output
+unescape() {
+    echo "$1" | sed 's/\\x3a/:/'
+}
+
+# remove all lines with specified string from specified file
+remove_line() {
+    local file
+    local string
+
+    file="$1"
+    string="$2"
+
+    if [ "$file" -a "$string" ] ; then
+       grep -v "$string" "$file" | sponge "$file"
+    fi
+}
+
+# translate ssh-style path variables %h and %u
+translate_ssh_variables() {
+    local uname
+    local home
+
+    uname="$1"
+    path="$2"
+
+    # get the user's home directory
+    userHome=$(getent passwd "$uname" | cut -d: -f6)
+
+    # translate '%u' to user name
+    path=${path/\%u/"$uname"}
+    # translate '%h' to user home directory
+    path=${path/\%h/"$userHome"}
+
+    echo "$path"
+}
+
+### CONVERTION UTILITIES
+
+# output the ssh key for a given key ID
+gpg2ssh() {
+    local keyID
+    
+    #keyID="$1" #TMP
+    # only use last 16 characters until openpgp2ssh can take all 40 #TMP
+    keyID=$(echo "$1" | cut -c 25-) #TMP
+
+    gpg --export "$keyID" | openpgp2ssh "$keyID" 2> /dev/null
+}
+
+# output the ssh key for a given secret key ID
+gpgsecret2ssh() {
+    local keyID
+
+    #keyID="$1" #TMP
+    # only use last 16 characters until openpgp2ssh can take all 40 #TMP
+    keyID=$(echo "$1" | cut -c 25-) #TMP
+
+    gpg --export-secret-key "$keyID" | openpgp2ssh "$keyID" 2> /dev/null
+}
+
+# output known_hosts line from ssh key
+ssh2known_hosts() {
+    local host
+    local key
+
+    host="$1"
+    key="$2"
+
+    echo -n "$host "
+    echo -n "$key" | tr -d '\n'
+    echo " MonkeySphere${DATE}"
+}
+
+# output authorized_keys line from ssh key
+ssh2authorized_keys() {
+    local userID
+    local key
+    
+    userID="$1"
+    key="$2"
+
+    echo -n "$key" | tr -d '\n'
+    echo " MonkeySphere${DATE} ${userID}"
+}
+
+# convert key from gpg to ssh known_hosts format
+gpg2known_hosts() {
+    local host
+    local keyID
+
+    host="$1"
+    keyID="$2"
+
+    # NOTE: it seems that ssh-keygen -R removes all comment fields from
+    # all lines in the known_hosts file.  why?
+    # NOTE: just in case, the COMMENT can be matched with the
+    # following regexp:
+    # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$'
+    echo -n "$host "
+    gpg2ssh "$keyID" | tr -d '\n'
+    echo " MonkeySphere${DATE}"
+}
+
+# convert key from gpg to ssh authorized_keys format
+gpg2authorized_keys() {
+    local userID
+    local keyID
+
+    userID="$1"
+    keyID="$2"
+
+    # NOTE: just in case, the COMMENT can be matched with the
+    # following regexp:
+    # '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}$'
+    gpg2ssh "$keyID" | tr -d '\n'
+    echo " MonkeySphere${DATE} ${userID}"
+}
+
+### GPG UTILITIES
+
+# retrieve all keys with given user id from keyserver
+# FIXME: need to figure out how to retrieve all matching keys
+# (not just first N (5 in this case))
+gpg_fetch_userid() {
+    local userID
+
+    userID="$1"
+
+    log -n " checking keyserver $KEYSERVER... "
+    echo 1,2,3,4,5 | \
+       gpg --quiet --batch --with-colons \
+       --command-fd 0 --keyserver "$KEYSERVER" \
+       --search ="$userID" > /dev/null 2>&1
+    loge "done."
+}
+
+# get the full fingerprint of a key ID
+get_key_fingerprint() {
+    local keyID
+
+    keyID="$1"
+
+    gpg --list-key --with-colons --fixed-list-mode \
+       --with-fingerprint --with-fingerprint "$keyID" | \
+       grep '^fpr:' | grep "$keyID" | cut -d: -f10
+}
+
+########################################################################
+### PROCESSING FUNCTIONS
+
+# userid and key policy checking
+# the following checks policy on the returned keys
+# - checks that full key has appropriate valididy (u|f)
+# - checks key has specified capability (REQUIRED_*_KEY_CAPABILITY)
+# - checks that requested user ID has appropriate validity
+# (see /usr/share/doc/gnupg/DETAILS.gz)
+# output is one line for every found key, in the following format:
+#
+# flag fingerprint
+#
+# "flag" is an acceptability flag, 0 = ok, 1 = bad
+# "fingerprint" is the fingerprint of the key
+#
+# expects global variable: "MODE"
+process_user_id() {
+    local userID
+    local requiredCapability
+    local requiredPubCapability
+    local gpgOut
+    local type
+    local validity
+    local keyid
+    local uidfpr
+    local usage
+    local keyOK
+    local uidOK
+    local lastKey
+    local lastKeyOK
+    local fingerprint
+
+    userID="$1"
+
+    # set the required key capability based on the mode
+    if [ "$MODE" = 'known_hosts' ] ; then
+       requiredCapability="$REQUIRED_HOST_KEY_CAPABILITY"
+    elif [ "$MODE" = 'authorized_keys' ] ; then
+       requiredCapability="$REQUIRED_USER_KEY_CAPABILITY"      
+    fi
+    requiredPubCapability=$(echo "$requiredCapability" | tr "[:lower:]" "[:upper:]")
+
+    # if CHECK_KEYSERVER variable set, check the keyserver
+    # for the user ID
+    if [ "$CHECK_KEYSERVER" = "true" ] ; then
+       gpg_fetch_userid "$userID"
+    fi
+
+    # output gpg info for (exact) userid and store
+    gpgOut=$(gpg --list-key --fixed-list-mode --with-colon \
+       --with-fingerprint --with-fingerprint \
+       ="$userID" 2>/dev/null)
+
+    # if the gpg query return code is not 0, return 1
+    if [ "$?" -ne 0 ] ; then
+        log "  - key not found."
+        return 1
+    fi
+
+    # loop over all lines in the gpg output and process.
+    # need to do it this way (as opposed to "while read...") so that
+    # variables set in loop will be visible outside of loop
+    echo "$gpgOut" | cut -d: -f1,2,5,10,12 | \
+    while IFS=: read -r type validity keyid uidfpr usage ; do
+       # process based on record type
+       case $type in
+           'pub') # primary keys
+               # new key, wipe the slate
+               keyOK=
+               uidOK=
+               lastKey=pub
+               lastKeyOK=
+               fingerprint=
+
+               log " primary key found: $keyid"
+
+               # if overall key is not valid, skip
+               if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
+                   log "  - unacceptable primary key validity ($validity)."
+                   continue
+               fi
+               # if overall key is disabled, skip
+               if check_capability "$usage" 'D' ; then
+                   log "  - key disabled."
+                   continue
+               fi
+               # if overall key capability is not ok, skip
+               if ! check_capability "$usage" $requiredPubCapability ; then
+                   log "  - unacceptable primary key capability ($usage)."
+                   continue
+               fi
+
+               # mark overall key as ok
+               keyOK=true
+
+               # mark primary key as ok if capability is ok
+               if check_capability "$usage" $requiredCapability ; then
+                   lastKeyOK=true
+               fi
+               ;;
+           'uid') # user ids
+               # if an acceptable user ID was already found, skip
+               if [ "$uidOK" ] ; then
+                   continue
+               fi
+               # if the user ID does not match, skip
+               if [ "$(unescape "$uidfpr")" != "$userID" ] ; then
+                   continue
+               fi
+               # if the user ID validity is not ok, skip
+               if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
+                   continue
+               fi
+
+               # mark user ID acceptable
+               uidOK=true
+
+               # output a line for the primary key
+               # 0 = ok, 1 = bad
+               if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then
+                   log "  * acceptable key found."
+                   echo 0 "$fingerprint"
+               else
+                   echo 1 "$fingerprint"
+               fi
+               ;;
+           'sub') # sub keys
+               # unset acceptability of last key
+               lastKey=sub
+               lastKeyOK=
+               fingerprint=
+
+               # if sub key validity is not ok, skip
+               if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then
+                   continue
+               fi
+               # if sub key capability is not ok, skip
+               if ! check_capability "$usage" $requiredCapability ; then
+                   continue
+               fi
+
+               # mark sub key as ok
+               lastKeyOK=true
+               ;;
+           'fpr') # key fingerprint
+               fingerprint="$uidfpr"
+
+               # if the last key was the pub key, skip
+               if [ "$lastKey" = pub ] ; then
+                   continue
+               fi
+               
+               # output a line for the last subkey
+               # 0 = ok, 1 = bad
+               if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then
+                   log "  * acceptable key found."
+                   echo 0 "$fingerprint"
+               else
+                   echo 1 "$fingerprint"
+               fi
+               ;;
+       esac
+    done
+}
+
+# process hosts in the known_host file
+process_hosts_known_hosts() {
+    local host
+    local userID
+    local ok
+    local keyid
+    local tmpfile
+
+    # create a lockfile on known_hosts
+    lockfile-create "$KNOWN_HOSTS"
+
+    for host ; do
+       log "processing host: $host"
+
+       userID="ssh://${host}"
+
+       process_user_id "ssh://${host}" | \
+       while read -r ok keyid ; do
+           sshKey=$(gpg2ssh "$keyid")
+           # remove the old host key line
+           remove_line "$KNOWN_HOSTS" "$sshKey"
+           # if key OK, add new host line
+           if [ "$ok" -eq '0' ] ; then
+               # hash if specified
+               if [ "$HASH_KNOWN_HOSTS" = 'true' ] ; then
+                   # FIXME: this is really hackish cause ssh-keygen won't
+                   # hash from stdin to stdout
+                   tmpfile=$(mktemp)
+                   ssh2known_hosts "$host" "$sshKey" > "$tmpfile"
+                   ssh-keygen -H -f "$tmpfile" 2> /dev/null
+                   cat "$tmpfile" >> "$KNOWN_HOSTS"
+                   rm -f "$tmpfile" "${tmpfile}.old"
+               else
+                   ssh2known_hosts "$host" "$sshKey" >> "$KNOWN_HOSTS"
+               fi
+           fi
+       done
+       # touch the lockfile, for good measure.
+       lockfile-touch --oneshot "$KNOWN_HOSTS"
+    done
+
+    # remove the lockfile
+    lockfile-remove "$KNOWN_HOSTS"
+}
+
+# process uids for the authorized_keys file
+process_uids_authorized_keys() {
+    local userID
+    local ok
+    local keyid
+
+    # create a lockfile on authorized_keys
+    lockfile-create "$AUTHORIZED_KEYS"
+
+    for userID ; do
+       log "processing user ID: $userID"
+
+       process_user_id "$userID" | \
+       while read -r ok keyid ; do
+           sshKey=$(gpg2ssh "$keyid")
+           # remove the old host key line
+           remove_line "$AUTHORIZED_KEYS" "$sshKey"
+           # if key OK, add new host line
+           if [ "$ok" -eq '0' ] ; then
+               ssh2authorized_keys "$userID" "$sshKey" >> "$AUTHORIZED_KEYS"
+           fi
+       done
+       # touch the lockfile, for good measure.
+       lockfile-touch --oneshot "$AUTHORIZED_KEYS"
+    done
+
+    # remove the lockfile
+    lockfile-remove "$AUTHORIZED_KEYS"
+}
+
+# process known_hosts file
+# go through line-by-line, extract each host, and process with the
+# host processing function
+process_known_hosts() {
+    local hosts
+    local host
+
+    # take all the hosts from the known_hosts file (first field),
+    # grep out all the hashed hosts (lines starting with '|')...
+    cat "$KNOWN_HOSTS" | meat | \
+       cut -d ' ' -f 1 | grep -v '^|.*$' | \
+    while IFS=, read -r -a hosts ; do
+       process_hosts_known_hosts ${hosts[@]}
+    done
+}
+
+# process an authorized_user_ids file for authorized_keys
+process_authorized_user_ids() {
+    local userid
+
+    authorizedUserIDs="$1"
+
+    cat "$authorizedUserIDs" | meat | \
+    while read -r userid ; do
+       process_uids_authorized_keys "$userid"
+    done
+}
+
+# EXPERIMENTAL (unused) process userids found in authorized_keys file
+# go through line-by-line, extract monkeysphere userids from comment
+# fields, and process each userid
+# NOT WORKING
+process_authorized_keys() {
+    local authorizedKeys
+    local userID
+
+    authorizedKeys="$1"
+
+    # take all the monkeysphere userids from the authorized_keys file
+    # comment field (third field) that starts with "MonkeySphere uid:"
+    # FIXME: needs to handle authorized_keys options (field 0)
+    cat "$authorizedKeys" | meat | \
+    while read -r options keytype key comment ; do
+       # if the comment field is empty, assume the third field was
+       # the comment
+       if [ -z "$comment" ] ; then
+           comment="$key"
+       fi
+
+       if echo "$comment" | egrep -v -q '^MonkeySphere[[:digit:]]{4}(-[[:digit:]]{2}){2}T[[:digit:]]{2}(:[[:digit:]]{2}){2}' ; then
+           continue
+       fi
+       userID=$(echo "$comment" | awk "{ print $2 }")
+       if [ -z "$userID" ] ; then
+           continue
+       fi
+
+       # process the userid
+       log "processing userid: '$userID'"
+       process_user_id "$userID" > /dev/null
+    done
+}
+
+##################################################
+### GPG HELPER FUNCTIONS
+
+# retrieve key from web of trust, and set owner trust to "full"
+# if key is found.
+trust_key() {
+    # get the key from the key server
+    if ! gpg --keyserver "$KEYSERVER" --recv-key "$keyID" ; then
+       failure "Could not retrieve key '$keyID'."
+    fi
+
+    # get key fingerprint
+    fingerprint=$(get_key_fingerprint "$keyID")
+
+    # attach a "non-exportable" signature to the key
+    # this is required for the key to have any validity at all
+    # the 'y's on stdin indicates "yes, i really want to sign"
+    echo -e 'y\ny' | gpg --lsign-key --command-fd 0 "$fingerprint"
+
+    # import "full" trust for fingerprint into gpg
+    echo ${fingerprint}:5: | gpg --import-ownertrust
+    if [ $? = 0 ] ; then
+       log "Owner trust updated."
+    else
+       failure "There was a problem changing owner trust."
+    fi 
+}
+
+# publish server key to keyserver
+publish_server_key() {
+    read -p "really publish key to $KEYSERVER? [y|N]: " OK; OK=${OK:=N}
+    if [ ${OK/y/Y} != 'Y' ] ; then
+       failure "aborting."
+    fi
+
+    # publish host key
+    # FIXME: need to figure out better way to identify host key
+    # dummy command so as not to publish fakes keys during testing
+    # eventually:
+    #gpg --keyserver "$KEYSERVER" --send-keys $(hostname -f)
+    failure "NOT PUBLISHED (to avoid permanent publication errors during monkeysphere development).
+To publish manually, do: gpg --keyserver $KEYSERVER --send-keys $(hostname -f)"
+}
diff --git a/src/keytrans/Makefile b/src/keytrans/Makefile
new file mode 100644 (file)
index 0000000..79602ef
--- /dev/null
@@ -0,0 +1,12 @@
+all: openpgp2ssh
+
+openpgp2ssh: openpgp2ssh.c gnutls-helpers.o
+       gcc -g -Wall --pedantic -o openpgp2ssh openpgp2ssh.c `libgnutls-config --libs --cflags` gnutls-helpers.o
+
+%.o: %.c
+       gcc -g -Wall --pedantic -o $@ -c $<
+
+clean: 
+       rm -f openpgp2ssh *.o
+
+.PHONY: clean all
diff --git a/src/keytrans/gnutls-helpers.c b/src/keytrans/gnutls-helpers.c
new file mode 100644 (file)
index 0000000..5b4c46a
--- /dev/null
@@ -0,0 +1,446 @@
+/* Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net> */
+/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */
+/* License: GPL v3 or later */
+
+#include "gnutls-helpers.h"
+/* for htonl() */
+#include <arpa/inet.h>
+
+/* for setlocale() */
+#include <locale.h>
+
+/* for isalnum() */
+#include <ctype.h>
+
+/* for exit() */
+#include <unistd.h>
+
+#include <assert.h>
+
+/* higher levels allow more frivolous error messages through. 
+   this is set with the MONKEYSPHERE_DEBUG variable */
+static int loglevel = 0;
+
+void err(int level, const char* fmt, ...) {
+  va_list ap;
+  if (level > loglevel)
+    return;
+  va_start(ap, fmt);
+  vfprintf(stderr, fmt, ap);
+  va_end(ap);
+  fflush(stderr);
+}
+
+void logfunc(int level, const char* string) {
+  fprintf(stderr, "GnuTLS Logging (%d): %s\n", level, string);
+}
+
+void init_keyid(gnutls_openpgp_keyid_t keyid) {
+  memset(keyid, 'x', sizeof(gnutls_openpgp_keyid_t));
+}
+
+
+
+void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid)
+{
+  assert(sizeof(out) >= 2*sizeof(keyid));
+  hex_print_data((char*)out, (const char*)keyid, sizeof(keyid));
+}
+
+/* you must have twice as many bytes in the out buffer as in the in buffer */
+void hex_print_data(char* out, const char* in, size_t incount)
+{
+  static const char hex[16] = "0123456789ABCDEF";
+  unsigned int inix = 0, outix = 0;
+  
+  while (inix < incount) {
+    out[outix] = hex[(in[inix] >> 4) & 0x0f];
+    out[outix + 1] = hex[in[inix] & 0x0f];
+    inix++;
+    outix += 2;
+  }
+}
+
+unsigned char hex2bin(unsigned char x) {
+  if ((x >= '0') && (x <= '9')) 
+    return x - '0';
+  if ((x >= 'A') && (x <= 'F')) 
+    return 10 + x - 'A';
+  if ((x >= 'a') && (x <= 'f')) 
+    return 10 + x - 'a';
+  return 0xff;
+}
+
+void collapse_printable_keyid(gnutls_openpgp_keyid_t out, printable_keyid in) {
+  unsigned int pkix = 0, outkix = 0;
+  
+  while (pkix < sizeof(printable_keyid)) {
+    unsigned hi = hex2bin(in[pkix]);
+    unsigned lo = hex2bin(in[pkix + 1]);
+    if (hi == 0xff) {
+      err(0, "character '%c' is not a hex char\n", in[pkix]);
+      exit(1);
+    }
+    if (lo == 0xff) {
+      err(0, "character '%c' is not a hex char\n", in[pkix + 1]);
+      exit(1);
+    }
+    out[outkix] = lo | (hi << 4);
+
+    pkix += 2;
+    outkix++;
+  }
+}
+
+int convert_string_to_keyid(gnutls_openpgp_keyid_t out, const char* str) {
+  printable_keyid p;
+  int ret;
+
+  ret = convert_string_to_printable_keyid(p, str);
+  if (ret == 0) 
+    collapse_printable_keyid(out, p);
+  return ret;
+}
+int convert_string_to_printable_keyid(printable_keyid pkeyid, const char* str) {
+  int arglen, x;
+  arglen = 0;
+  x = 0;
+  while ((arglen <= sizeof(printable_keyid)) &&
+        (str[x] != '\0')) {
+    if (isxdigit(str[x])) {
+      if (arglen == sizeof(printable_keyid)) {
+       err(0, "There are more than %d hex digits in the keyid '%s'\n", sizeof(printable_keyid), str);
+       return 1;
+      }
+      pkeyid[arglen] = str[x];
+      arglen++;
+    }
+    x++;
+  }
+  
+  if (arglen != sizeof(printable_keyid)) {
+    err(0, "Keyid '%s' is not %d hex digits in length\n", str, sizeof(printable_keyid));
+    return 1;
+  }
+  return 0;
+}
+
+
+
+int init_gnutls() {
+  const char* version = NULL;
+  const char* debug_string = NULL;
+  int ret;
+
+  if (debug_string = getenv("MONKEYSPHERE_DEBUG"), debug_string) {
+    loglevel = atoi(debug_string);
+  }
+
+  if (ret = gnutls_global_init(), ret) {
+    err(0, "Failed to do gnutls_global_init() (error: %d)\n", ret);
+    return 1;
+  }
+
+  version = gnutls_check_version(NULL);
+
+  if (version) 
+    err(1, "gnutls version: %s\n", version);
+  else {
+    err(0, "no gnutls version found!\n");
+    return 1;
+  }
+
+  gnutls_global_set_log_function(logfunc);
+  
+  gnutls_global_set_log_level(loglevel);
+  err(1, "set log level to %d\n", loglevel);
+
+  return 0;
+}
+
+void init_datum(gnutls_datum_t* d) {
+  d->data = NULL;
+  d->size = 0;
+}
+void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src) {
+  dest->data = gnutls_realloc(dest->data, src->size);
+  dest->size = src->size;
+  memcpy(dest->data, src->data, src->size);
+}
+int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b) {
+  if (a->size > b->size) {
+    err(0,"a is larger\n");
+    return 1;
+  }
+  if (a->size < b->size) {
+    err(0,"b is larger\n");
+    return -1;
+  }
+  return memcmp(a->data, b->data, a->size);
+}
+void free_datum(gnutls_datum_t* d) {
+  gnutls_free(d->data);
+  d->data = NULL;
+  d->size = 0;
+}
+
+/* read the passed-in string, store in a single datum */
+int set_datum_string(gnutls_datum_t* d, const char* s) {
+  unsigned int x = strlen(s)+1;
+  unsigned char* c = NULL;
+
+  c = gnutls_realloc(d->data, x);
+  if (NULL == c)
+    return -1;
+  d->data = c;
+  d->size = x;
+  memcpy(d->data, s, x);
+  return 0;
+}
+
+/* read the passed-in file descriptor until EOF, store in a single
+   datum */
+int set_datum_fd(gnutls_datum_t* d, int fd) {
+  unsigned int bufsize = 1024;
+  unsigned int len = 0;
+
+  FILE* f = fdopen(fd, "r");
+  if (bufsize > d->size) {
+    bufsize = 1024;
+    d->data = gnutls_realloc(d->data, bufsize);
+    if (d->data == NULL) {
+      err(0,"out of memory!\n");
+      return -1;
+    }
+    d->size = bufsize;
+  } else {
+    bufsize = d->size;
+  }
+  f = fdopen(fd, "r");
+  if (NULL == f) {
+    err(0,"could not fdopen FD %d\n", fd);
+  }
+  clearerr(f);
+  while (!feof(f) && !ferror(f)) { 
+    if (len == bufsize) {
+      /* allocate more space by doubling: */
+      bufsize *= 2;
+      d->data = gnutls_realloc(d->data, bufsize);
+      if (d->data == NULL) {
+       err(0,"out of memory!\n"); 
+       return -1;
+      };
+      d->size = bufsize;
+    }
+    len += fread(d->data + len, 1, bufsize - len, f);
+    /*     err(0,"read %d bytes\n", len); */
+  }
+  if (ferror(f)) {
+    err(0,"Error reading from fd %d (error: %d) (error: %d '%s')\n", fd, ferror(f), errno, strerror(errno));
+    return -1;
+  }
+    
+  /* touch up buffer size to match reality: */
+  d->data = gnutls_realloc(d->data, len);
+  d->size = len;
+  return 0;
+}
+
+/* read the file indicated (by name) in the fname parameter.  store
+   its entire contents in a single datum. */
+int set_datum_file(gnutls_datum_t* d, const char* fname) {
+  struct stat sbuf;
+  unsigned char* c = NULL;
+  FILE* file = NULL;
+  size_t x = 0;
+
+  if (0 != stat(fname, &sbuf)) {
+    err(0,"failed to stat '%s'\n", fname);
+    return -1;
+  }
+  
+  c = gnutls_realloc(d->data, sbuf.st_size);
+  if (NULL == c) {
+    err(0,"failed to allocate %d bytes for '%s'\n", sbuf.st_size, fname);
+    return -1;
+  }
+
+  d->data = c;
+  d->size = sbuf.st_size;
+  file = fopen(fname, "r");
+  if (NULL == file) {
+    err(0,"failed to open '%s' for reading\n",  fname);
+    return -1;
+  }
+
+  x = fread(d->data, d->size, 1, file);
+  if (x != 1) {
+    err(0,"tried to read %d bytes, read %d instead from '%s'\n", d->size, x, fname);
+    fclose(file);
+    return -1;
+  }
+  fclose(file);
+  return 0;
+}
+
+int write_datum_fd(int fd, const gnutls_datum_t* d) {
+  if (d->size != write(fd, d->data, d->size)) {
+    err(0,"failed to write body of datum.\n");
+    return -1;
+  }
+  return 0;
+}
+
+
+int write_datum_fd_with_length(int fd, const gnutls_datum_t* d) {
+  uint32_t len;
+  int looks_negative = (d->data[0] & 0x80);
+  unsigned char zero = 0;
+
+  /* if the first bit is 1, then the datum will appear negative in the
+     MPI encoding style used by OpenSSH.  In that case, we'll increase
+     the length by one, and dump out one more byte */
+
+  if (looks_negative) {
+    len = htonl(d->size + 1);
+  } else {
+    len = htonl(d->size);
+  }
+  if (write(fd, &len, sizeof(len)) != sizeof(len)) {
+    err(0,"failed to write size of datum.\n");
+    return -2;
+  }
+  if (looks_negative) {
+    if (write(fd, &zero, 1) != 1) {
+      err(0,"failed to write padding byte for MPI.\n");
+      return -2;
+    }
+  }
+  return write_datum_fd(fd, d);
+}
+
+int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num) {
+  unsigned int i;
+  int ret;
+
+  for (i = 0; i < num; i++)
+    if (ret = write_datum_fd_with_length(fd, d[i]), ret != 0)
+      return ret;
+
+  return 0;
+}
+
+
+int datum_from_string(gnutls_datum_t* d, const char* str) {
+  d->size = strlen(str);
+  d->data = gnutls_realloc(d->data, d->size);
+  if (d->data == 0)
+    return ENOMEM;
+  memcpy(d->data, str, d->size);
+  return 0;
+}
+
+
+int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]) {
+  int p[2];
+  int ret;
+
+  if (pid == NULL) {
+    err(0,"bad pointer passed to create_writing_pipe()\n");
+    return -1;
+  }
+
+  if (ret = pipe(p), ret == -1) {
+    err(0,"failed to create a pipe (error: %d \"%s\")\n", errno, strerror(errno));
+    return -1;
+  }
+
+  *pid = fork();
+  if (*pid == -1) {
+    err(0,"Failed to fork (error: %d \"%s\")\n", errno, strerror(errno));
+    return -1;
+  }
+  if (*pid == 0) { /* this is the child */
+    close(p[1]); /* close unused write end */
+    
+    if (0 != dup2(p[0], 0)) { /* map the reading end into stdin */
+      err(0,"Failed to transfer reading file descriptor to stdin (error: %d \"%s\")\n", errno, strerror(errno));
+      exit(1);
+    }
+    execv(path, argv);
+    err(0,"exec %s failed (error: %d \"%s\")\n", path, errno, strerror(errno));
+    /* close the open file descriptors */
+    close(p[0]);
+    close(0);
+
+    exit(1);
+  } else { /* this is the parent */
+    close(p[0]); /* close unused read end */
+    return p[1];
+  }
+}
+
+int validate_ssh_host_userid(const char* userid) {
+  char* oldlocale = setlocale(LC_ALL, "C");
+  
+  /* choke if userid does not match the expected format
+     ("ssh://fully.qualified.domain.name") */
+  if (strncmp("ssh://", userid, strlen("ssh://")) != 0) {
+    err(0,"The user ID should start with ssh:// for a host key\n");
+    goto fail;
+  }
+  /* so that isalnum will work properly */
+  userid += strlen("ssh://");
+  while (0 != (*userid)) {
+    if (!isalnum(*userid)) {
+      err(0,"label did not start with a letter or a digit! (%s)\n", userid);
+      goto fail;
+    }
+    userid++;
+    while (isalnum(*userid) || ('-' == (*userid)))
+      userid++;
+    if (('.' == (*userid)) || (0 == (*userid))) { /* clean end of label:
+                                                check last char
+                                                isalnum */
+      if (!isalnum(*(userid - 1))) {
+       err(0,"label did not end with a letter or a digit!\n");
+       goto fail;
+      }
+      if ('.' == (*userid)) /* advance to the start of the next label */
+       userid++;
+    } else {
+      err(0,"invalid character in domain name: %c\n", *userid);
+      goto fail;
+    }
+  }
+  /* ensure that the last character is valid: */
+  if (!isalnum(*(userid - 1))) {
+    err(0,"hostname did not end with a letter or a digit!\n");
+    goto fail;
+  }
+  /* FIXME: fqdn's can be unicode now, thanks to RFC 3490 -- how do we
+     make sure that we've got an OK string? */
+
+  return 0;
+
+ fail:
+  setlocale(LC_ALL, oldlocale);
+  return 1;
+}
+
+/* http://tools.ietf.org/html/rfc4880#section-5.5.2 */
+size_t get_openpgp_mpi_size(gnutls_datum_t* d) {
+  return 2 + d->size;
+}
+
+int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d) {
+  uint16_t x;
+
+  x = d->size * 8;
+  x = htons(x);
+  
+  write(fd, &x, sizeof(x));
+  write(fd, d->data, d->size);
+  
+  return 0;
+}
diff --git a/src/keytrans/gnutls-helpers.h b/src/keytrans/gnutls-helpers.h
new file mode 100644 (file)
index 0000000..f196456
--- /dev/null
@@ -0,0 +1,78 @@
+/* Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net> */
+/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */
+/* License: GPL v3 or later */
+
+
+#include <gnutls/gnutls.h>
+#include <gnutls/openpgp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+/* Functions to help dealing with GnuTLS for monkeysphere key
+   translation projects: */
+
+/* set everything up, including logging levels.  Return 0 on
+   success */
+int init_gnutls();
+
+/* logging and output functions: */
+
+void err(int level, const char* fmt, ...);
+void logfunc(int level, const char* string);
+
+/* basic datum manipulations: */
+
+void init_datum(gnutls_datum_t* d);
+void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src);
+int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b);
+void free_datum(gnutls_datum_t* d);
+int write_datum_fd(int fd, const gnutls_datum_t* d);
+int write_datum_fd_with_length(int fd, const gnutls_datum_t* d);
+int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num);
+
+/* set up a datum from a null-terminated string */
+int datum_from_string(gnutls_datum_t* d, const char* str);
+
+/* keyid manipulations: */
+typedef unsigned char printable_keyid[16];
+
+void init_keyid(gnutls_openpgp_keyid_t keyid);
+void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid);
+void collapse_printable_keyid(gnutls_openpgp_keyid_t out, printable_keyid in);
+int convert_string_to_keyid(gnutls_openpgp_keyid_t out, const char* str);
+int convert_string_to_printable_keyid(printable_keyid out, const char* str);
+
+/* you must have twice as many bytes in the out buffer as in the in buffer */
+void hex_print_data(char* out, const char* in, size_t incount);
+
+/* functions to get data into datum objects: */
+
+/* read the passed-in string, store in a single datum */
+int set_datum_string(gnutls_datum_t* d, const char* s);
+
+/* read the passed-in file descriptor until EOF, store in a single
+   datum */
+int set_datum_fd(gnutls_datum_t* d, int fd);
+
+/* read the file indicated (by name) in the fname parameter.  store
+   its entire contents in a single datum. */
+int set_datum_file(gnutls_datum_t* d, const char* fname);
+
+/* set up file descriptor pipe for writing (child process pid gets
+   stored in pid, fd is returned)*/
+int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]);
+
+/* return 0 if userid matches the monkeysphere spec for ssh host user IDs */
+int validate_ssh_host_userid(const char* userid);
+
+/* how many bytes will it take to write out this datum in OpenPGP MPI form? */
+size_t get_openpgp_mpi_size(gnutls_datum_t* d);
+
+/* write the MPI stored in gnutls_datum_t to file descriptor fd: */
+int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d);
diff --git a/src/keytrans/openpgp2ssh.c b/src/keytrans/openpgp2ssh.c
new file mode 100644 (file)
index 0000000..92bdc19
--- /dev/null
@@ -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 not contain any form of OpenPGP key I recognize.\n");
+      return 1;
+    }
+  }
+
+  gnutls_global_deinit();
+  return 0;
+}
diff --git a/src/monkeysphere b/src/monkeysphere
new file mode 100755 (executable)
index 0000000..9b315e2
--- /dev/null
@@ -0,0 +1,204 @@
+#!/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-authorized_keys (a)        update authorized_keys file
+  gen-subkey (g) KEYID              generate an 'a' capable subkey
+  help (h,?)                        this help
+
+EOF
+}
+
+# generate a subkey with the 'a' usage flags set
+# FIXME: this needs some tweaking to clean it up
+gen_subkey(){
+    local keyID
+    local gpgOut
+    local userID
+
+    keyID="$1"
+
+    gpgOut=$(gpg --quiet --fixed-list-mode --list-keys --with-colons \
+       "$keyID" 2> /dev/null)
+
+    # fail if there only "tru" lines are output from gpg, which
+    # indicates the key was not found.
+    if [ -z "$(echo "$gpgOut" | grep -v '^tru:')" ] ; then
+       failure "Key ID '$keyID' not found."
+    fi
+
+    # fail if multiple pub lines are returned, which means the id given
+    # is not unique
+    if [ $(echo "$gpgOut" | grep '^pub:' | wc -l) -gt '1' ] ; then
+       failure "Key ID '$keyID' is not unique."
+    fi
+
+    # prompt if an authentication subkey already exists
+    if echo "$gpgOut" | egrep "^(pub|sub):" | cut -d: -f 12 | grep -q a ; then
+       echo "An authentication subkey already exists for key '$keyID'."
+       read -p "Are you sure you would like to generate another one? [y|N]: " OK; OK=${OK:N}
+       if [ "${OK/y/Y}" != 'Y' ] ; then
+           failure "aborting."
+       fi
+    fi
+
+    # set subkey defaults
+    SUBKEY_TYPE=${SUBKEY_TYPE:-"RSA"}
+    SUBKEY_LENGTH=${SUBKEY_LENGTH:-}
+    SUBKEY_USAGE=${SUBKEY_USAGE:-"auth"}
+    SUBKEY_EXPIRE=${SUBKEY_EXPIRE:-"0"}
+    cat <<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:-"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
+           process_hosts_known_hosts "$@"
+
+        # otherwise, if no hosts are specified, process every host
+        # 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
+       fi
+
+       log "known_hosts file updated."
+       ;;
+
+    '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 "$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 (executable)
index 0000000..40a6b54
--- /dev/null
@@ -0,0 +1,264 @@
+#!/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
+  show-fingerprint (f)                  show server's host key fingerprint
+  publish-key (p)                       publish server key to keyserver
+  trust-keys (t) KEYID...               mark keyids as trusted
+  help (h,?)                            this help
+
+EOF
+}
+
+# generate server gpg key
+gen_key() {
+    local hostName
+
+    hostName=${1:-$(hostname --fqdn)}
+    service=${SERVICE:-"ssh"}
+    userID="${service}://${hostName}"
+
+    if gpg --list-key ="$userID" > /dev/null 2>&1 ; then
+       failure "Key for '$userID' already exists"
+    fi
+
+    # set key defaults
+    KEY_TYPE=${KEY_TYPE:-"RSA"}
+    KEY_LENGTH=${KEY_LENGTH:-"2048"}
+    KEY_USAGE=${KEY_USAGE:-"auth"}
+    KEY_EXPIRE=${KEY_EXPIRE:-"0"}
+    cat <<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? ($KEY_EXPIRE) " KEY_EXPIRE; KEY_EXPIRE=${KEY_EXPIRE:-"0"}
+
+    # set key parameters
+    keyParameters=$(cat <<EOF
+Key-Type: $KEY_TYPE
+Key-Length: $KEY_LENGTH
+Key-Usage: $KEY_USAGE
+Name-Real: $userID
+Expire-Date: $KEY_EXPIRE
+EOF
+)
+
+    # add the revoker field if requested
+    # FIXME: the "1:" below assumes that $REVOKER's key is an RSA key.  why?
+    # FIXME: why is this marked "sensitive"?  how will this signature ever
+    # be transmitted to the expected revoker?
+    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
+
+    # add commit command
+    keyParameters="${keyParameters}"$(cat <<EOF
+
+%commit
+%echo done
+EOF
+)
+
+    log "generating server key... "
+    echo "$keyParameters" | gpg --batch --gen-key
+
+    # output the server fingerprint
+    fingerprint_server_key "=${userID}"
+
+    # find the key fingerprint of the server primary key
+    keyID=$(gpg --list-key --with-colons --with-fingerprint "=${userID}" | \
+       grep '^fpr:' | head -1 | cut -d: -f10)
+
+    # write the key to the file
+    # NOTE: assumes that the primary key is the proper key to use
+    (umask 077 && gpgsecret2ssh "$keyID" > "${MS_HOME}/ssh_host_rsa_key")
+    log "Private SSH host key output to file: ${MS_HOME}/ssh_host_rsa_key"
+}
+
+# gpg output key fingerprint
+fingerprint_server_key() {
+    local ID
+
+    if [ -z "$1" ] ; then
+       ID="$1"
+    else
+       ID="=ssh://$(hostname --fqdn)"
+    fi
+
+    gpg --fingerprint --list-secret-keys "$ID"
+}
+
+########################################################################
+# MAIN
+########################################################################
+
+COMMAND="$1"
+[ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
+shift
+
+# set ms home directory
+MS_HOME=${MS_HOME:-"$ETC"}
+
+# load configuration file
+MS_CONF=${MS_CONF:-"$MS_HOME"/monkeysphere-server.conf}
+[ -e "$MS_CONF" ] && . "$MS_CONF"
+
+# set empty config variable with defaults
+GNUPGHOME=${GNUPGHOME:-"${MS_HOME}/gnupg"}
+KEYSERVER=${KEYSERVER:-"subkeys.pgp.net"}
+CHECK_KEYSERVER=${CHECK_KEYSERVER:="true"}
+REQUIRED_USER_KEY_CAPABILITY=${REQUIRED_USER_KEY_CAPABILITY:-"a"}
+AUTHORIZED_USER_IDS=${AUTHORIZED_USER_IDS:-"%h/.config/monkeysphere/authorized_user_ids"}
+USER_CONTROLLED_AUTHORIZED_KEYS=${USER_CONTROLLED_AUTHORIZED_KEYS:-"%h/.ssh/authorized_keys"}
+
+export GNUPGHOME
+
+# make sure the monkeysphere home directory exists
+mkdir -p "${MS_HOME}/authorized_user_ids"
+# make sure gpg home exists with proper permissions
+mkdir -p -m 0700 "$GNUPGHOME"
+# make sure the authorized_keys directory exists
+mkdir -p "${CACHE}/authorized_keys"
+
+case $COMMAND in
+    'update-users'|'update-user'|'s')
+       if [ "$1" ] ; then
+           # get users from command line
+           unames="$@"
+       else
+           # or just look at all users if none specified
+           unames=$(getent passwd | cut -d: -f1)
+       fi
+
+       # loop over users
+       for uname in $unames ; do
+           MODE="authorized_keys"
+
+           # check all specified users exist
+           if ! getent passwd "$uname" >/dev/null ; then
+               error "----- unknown user '$uname' -----"
+               continue
+           fi
+
+           # set authorized_user_ids variable,
+           # translate ssh-style path variables
+           authorizedUserIDs=$(translate_ssh_variables "$uname" "$AUTHORIZED_USER_IDS")
+
+           # skip user if authorized_user_ids file does not exist
+           if [ ! -f "$authorizedUserIDs" ] ; then
+               continue
+           fi
+
+           log "----- user: $uname -----"
+
+           # temporary authorized_keys file
+           AUTHORIZED_KEYS=$(mktemp)
+
+           # skip if the user's authorized_user_ids file is empty
+           if [ ! -s "$authorizedUserIDs" ] ; then
+               log "authorized_user_ids file '$authorizedUserIDs' is empty."
+               continue
+           fi
+
+           # process authorized_user_ids file
+           log "processing authorized_user_ids file..."
+           process_authorized_user_ids "$authorizedUserIDs"
+
+           # add user-controlled authorized_keys file path if specified
+           if [ "$USER_CONTROLLED_AUTHORIZED_KEYS" != '-' ] ; then
+               userAuthorizedKeys=$(translate_ssh_variables "$uname" "$USER_CONTROLLED_AUTHORIZED_KEYS")
+               if [ -f "$userAuthorizedKeys" ] ; then
+                   log -n "adding user's authorized_keys file... "
+                   cat "$userAuthorizedKeys" >> "$AUTHORIZED_KEYS"
+                   loge "done."
+               fi
+           fi
+
+           # move the temp authorized_keys file into place
+           mv -f "$AUTHORIZED_KEYS" "${CACHE}/authorized_keys/${uname}"
+
+           log "authorized_keys file updated."
+       done
+       ;;
+
+    'gen-key'|'g')
+       gen_key "$1"
+       ;;
+
+    'show-fingerprint'|'f')
+       fingerprint_server_key "$@"
+       ;;
+
+    'publish-key'|'p')
+       publish_server_key
+       ;;
+
+    'trust-keys'|'trust-key'|'t')
+       if [ -z "$1" ] ; then
+           failure "You must specify at least one key to trust."
+       fi
+
+       # process key IDs
+       for keyID ; do
+           trust_key "$keyID"
+       done
+       ;;
+
+    'help'|'h'|'?')
+        usage
+        ;;
+
+    *)
+        failure "Unknown command: '$COMMAND'
+Type '$PGRM help' for usage."
+        ;;
+esac
+
+exit "$ERR"
diff --git a/src/monkeysphere-ssh-proxycommand b/src/monkeysphere-ssh-proxycommand
new file mode 100755 (executable)
index 0000000..f4d4b0d
--- /dev/null
@@ -0,0 +1,80 @@
+#!/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
+
+usage() {
+cat <<EOF >&2
+usage: ssh -o ProxyCommand="$(basename $0) %h %p" ...
+EOF
+}
+
+log() {
+    echo "$@" >&2
+}
+
+if [ "$1" = '--no-connect' ] ; then
+    NO_CONNECT='true'
+    shift 1
+fi
+
+HOST="$1"
+PORT="$2"
+
+if [ -z "$HOST" ] ; then
+    log "host must be specified."
+    usage
+    exit 1
+fi
+if [ -z "$PORT" ] ; then
+    log "port must be specified."
+    usage
+    exit 1
+fi
+
+# set the host URI
+URI="ssh://${HOST}"
+if [ "$PORT" != '22' ] ; then
+    URI="${URI}:$PORT"
+fi
+
+# if the host is in the gpg keyring...
+if gpg --list-key ="${URI}" 2>&1 >/dev/null ; then
+    # do not check the keyserver
+    CHECK_KEYSERVER="false"
+# if the host is NOT in the keyring...
+else
+    # if the host key is found in the known_hosts file...
+    # FIXME: this only works for default known_hosts location
+    hostKey=$(ssh-keygen -F "$HOST")
+    if [ "$hostKey" ] ; then
+       # if the check keyserver variable is NOT set to true...
+       if  [ "$CHECK_KEYSERVER" != 'true' ] ; then
+           # schedule a keyserver check for host at a later time
+           echo "monkeysphere update-known_hosts $HOST" | at noon
+       fi
+    # if the host key is not found in the known_hosts file...
+    else
+       # check the keyserver
+       CHECK_KEYSERVER="true"
+    fi
+fi
+export CHECK_KEYSERVER
+
+# update the known_hosts file for the host
+monkeysphere update-known_hosts "$HOST"
+
+# exec a netcat passthrough to host for the ssh connection
+if [ -z "$NO_CONNECT" ] ; then
+    exec nc "$HOST" "$PORT"
+fi
diff --git a/src/seckey2sshagent b/src/seckey2sshagent
new file mode 100755 (executable)
index 0000000..1266db5
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+# seckey2sshagent: this is a hack of a script to cope with the fact
+# that openpgp2ssh currently cannot support encrypted secret keys.
+
+# the basic operating principal is: 
+
+# export the secret key in encrypted format to a new keyring
+
+# remove the passphrase in that keyring
+
+# use that keyring with openpgp2ssh
+
+# Authors: Daniel Kahn Gillmor <dkg@fifthhorseman.net>,
+#          Jameson Rollins <jrollins@fifthhorseman.net>
+
+
+cleanup() {
+    echo -n "removing temp gpg home... " 1>&2
+    rm -rf $FOO
+    echo "done." 1>&2
+}
+
+trap cleanup EXIT
+
+#GPGID="$1"
+GPGID=$(echo "$1" | cut -c 25-)
+
+FOO=$(mktemp -d)
+
+gpg --export-secret-key $GPGID | GNUPGHOME="$FOO" gpg --import
+
+# idea to script the password stuff.  not working.
+# read -s -p "enter gpg password: " PASSWD; echo
+# cmd=$(cat <<EOF
+# passwd
+# $PASSWD
+# \n
+# \n
+# \n
+# yes
+# save
+# EOF
+# )
+# echo -e "$cmd" | GNUPGHOME="$FOO" gpg --command-fd 0 --edit-key $GPGID
+
+GNUPGHOME="$FOO" gpg --edit-key $GPGID
+
+ln -s /dev/stdin "$FOO"/openpgp
+
+GNUPGHOME="$FOO" gpg --export-secret-key $GPGID | \
+    openpgp2ssh $GPGID | ssh-add -c "$FOO"/openpgp
diff --git a/test.key b/test.key
deleted file mode 100644 (file)
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-----