Merge branch 'release/1.2.3'
commit
287f188340
|
@ -0,0 +1,832 @@
|
|||
<!--
|
||||
|
||||
Livejournal Introduction:
|
||||
|
||||
I recently wrestled with something, learned quite a lot, and came up with a document that I'm really rather proud of, that
|
||||
shares knowledge that's not all out there in one place anywhere else. Along the way I've written some software that I'm
|
||||
releasing, that makes all of what I've learned a lot easier, and may help make the world a little more secure. I'd like to
|
||||
share it here.
|
||||
|
||||
This is going to be a technical post. For that I apologize. The target of this post is anyone who has a GPG key that they'd
|
||||
like to expand to a greater audience, and who controls DNS for any of the email domains they publish. Anyone that I host DNS
|
||||
or mail for is also welcome to do this, if you use PGP, as part of the goal of writing this is to encourage adoption and use
|
||||
of these methods
|
||||
|
||||
<lj-cut text="This will be long and technical">
|
||||
-->
|
||||
|
||||
# The complete guide to publishing PGP keys in DNS
|
||||
|
||||
## Introduction
|
||||
|
||||
Publishing PGP keys is a pain. There are many disjoint keyservers, three or
|
||||
four _networks_ of which, which do (or don't) share information with each
|
||||
other. Some are corporate, some are private. And it's a crapshoot as to
|
||||
whose key is going to be on which, or worse, which will have the latest copy
|
||||
of a person's key.
|
||||
|
||||
For a long time, GPG has had a way to publish keys in DNS, but it hasn't been
|
||||
well documented. This document hopes to change that.
|
||||
|
||||
After reading this, you should:
|
||||
|
||||
* Know the three ways to publish a key
|
||||
|
||||
* Have at least a couple tools to do so
|
||||
|
||||
* Have learned a bit more about DNS
|
||||
|
||||
The target audience for this guide is a technical one. It's expected you
|
||||
understand what DNS is, and what an RFC and a resource record is.
|
||||
|
||||
There are three ways to publish a PGP key in DNS. Most modern versions of GPG
|
||||
can retrieve from all three, although it's not enabled by default. There are
|
||||
no compile-time options you need to enable it, and it's simple to turn on. Of
|
||||
the three key-publishing methods, there are two that you probably shouldn't
|
||||
use at the same time, and there are advantages and disadvantages to each,
|
||||
which I hope to outline below, both in general and for each method.
|
||||
|
||||
### Advantages to DNS publishing of your keys
|
||||
|
||||
* It's universal. Your DNS is your own, and you don't have to worry about
|
||||
which network of vastly-disconnectedkeyservers is caching your key.
|
||||
|
||||
* Using DNS does not stop you from publishing via other means.
|
||||
|
||||
* If you run an organization, you can easily publish all your employee-keys
|
||||
via this method, and in the same step,define a signing-policy, such that a
|
||||
person need only assign trust to your organization's "keysigning key" (or
|
||||
theCEO's key, or the CTO's), without the trouble of running a keyserver.
|
||||
|
||||
* DNSSEC can be (somewhat) used as an additional trust-path vector. More on
|
||||
this in the notes at the bottom.
|
||||
|
||||
* You do not have to be searching DNS for keys in order to publish. On the
|
||||
same note, you do not have to be publishing in this manner to search
|
||||
forothers there.
|
||||
|
||||
### Disadvantages to DNS publishing
|
||||
|
||||
* If you don't control your own DNS (or have a good relationship with your DNS
|
||||
admin), this isn't going to beas easy or even possible. Ideally, you want
|
||||
to be running BIND.
|
||||
|
||||
* With two of the three methods listed here, you're going to need to be able
|
||||
to put a CERT record into your DNS. Mostweb-enabled DNS tools probably will
|
||||
not give you this ability. The third uses TXT records, which SPF has caused
|
||||
to befairly universal in web-interfaces. However, it's also the least
|
||||
standards-defined of the three.
|
||||
|
||||
* Using at least some of these methods, it's not always a "set it and forget
|
||||
it" procedure. You may need toperiodically re-export your key and
|
||||
re-publish it, especially if you gain new signatures.
|
||||
|
||||
* Using some of these methods, you're going to be putting some pretty large,
|
||||
pretty unwiedly lines in your DNS zones. Not everyone will easily be able
|
||||
to retrieve them, but again, you can still publish other ways.
|
||||
|
||||
* Using some of these methods, DNS is just a means to an end: you still need
|
||||
to publish your key elsewhere, like a webpage,and the DNS records just point
|
||||
at it.
|
||||
|
||||
* Initial verifications of most of these seem to imply that only DSA keys are
|
||||
supported, although I welcome feedback. Itseems the community is trying to
|
||||
get RSA keys to make a comeback. They're the only type supported by the
|
||||
gpg2.0 card, andthey are the default keytype. There was a while where they
|
||||
weren't, though. Since writing this document, I've discoveredthat "new" RSA
|
||||
keys work, but ancient RSA keys with no subkeys tend to misbehave.
|
||||
|
||||
### Turning on key-fetching via DNS
|
||||
|
||||
Inside your GPG "options" file, find the "auto-key-locate" line, and add
|
||||
"cert" and/or "pka" to the options.
|
||||
|
||||
auto-key-locate cert pka (as well as other methods, like keyserver URLs)
|
||||
|
||||
|
||||
Don't be surprised if a lot of people don't use this method.
|
||||
|
||||
Note that you can also turn on two options during signature verification.
|
||||
They are specified in a "verify-options" clause in your config file, or on the
|
||||
command line, and they are (right from the GPG manpage):
|
||||
|
||||
pka-lookups
|
||||
|
||||
Enable PKA lookups to verify sender addresses. Note that
|
||||
PKA is based on DNS, and so enabling this option may dis-
|
||||
close information on when and what signatures are veri-
|
||||
fied or to whom data is encrypted. This is similar to the
|
||||
"web bug" described for the auto-key-retrieve feature.
|
||||
|
||||
And:
|
||||
|
||||
pka-trust-increase
|
||||
|
||||
Raise the trust in a signature to full if the signature
|
||||
passes PKA validation. This option is only meaningful if
|
||||
pka-lookups is set.
|
||||
|
||||
|
||||
You can also use the same options on the command line (as you'll see in this
|
||||
document).
|
||||
|
||||
## Types of PGP Key Records
|
||||
|
||||
### DNS PKA Records
|
||||
|
||||
Relevant RFCs: None that I can find.
|
||||
|
||||
Other Docs: The GPG source and mailing lists.
|
||||
|
||||
#### Advantages
|
||||
|
||||
* It's a TXT record. Easy to put in a zonefile with most management software.
|
||||
* No special tools required to generate, just three simple pieces of data.
|
||||
* Since it uses a special subzone, you can manage the _pka namespace in a
|
||||
separate zonefile.
|
||||
* GPG has an option, when verifying a signature, to look up these records
|
||||
(--verify-options pka-lookups), so it's doubly useful, both from a
|
||||
distribution and a verification point.
|
||||
|
||||
#### Disadvantages
|
||||
|
||||
* As with IPGP certs, you're at the mercy of the URL. This doesn't put your
|
||||
key in DNS, just the location of it, and the fingerprint. Some clients may
|
||||
not be able to support https or http 1.1.
|
||||
* Not RFC standard.
|
||||
|
||||
#### Howto
|
||||
|
||||
1. Figure out which key you want to export:
|
||||
|
||||
%gpg --list-keys danm@prime.gushi.org
|
||||
Warning: using insecure memory!
|
||||
pub 1024D/624BB249 2000-10-02 <-- I'm going to use this one.
|
||||
uid Daniel P. Mahoney <danm@prime.gushi.org>
|
||||
uid Daniel Mahoney (Secondary Email) <gushi@gushi.org>
|
||||
sub 2048g/DE20C529 2000-10-02
|
||||
pub 1024R/309C17C5 1997-05-08
|
||||
uid Daniel P. Mahoney <danm@prime.gushi.org>
|
||||
|
||||
2. Export the key to a file (I use keyid.pub.asc, but it can be anything)
|
||||
|
||||
%gpg --export --armor 624BB249 > 624BB249.pub.asc
|
||||
Warning: using insecure memory!
|
||||
%
|
||||
|
||||
3. Get the fingerprint for your key:
|
||||
|
||||
%gpg --list-keys --fingerprint 624BB249
|
||||
gpg: WARNING: using insecure memory!
|
||||
gpg: please see http://www.gnupg.org/faq.html for more information
|
||||
pub 1024D/624BB249 2000-10-02
|
||||
Key fingerprint = C206 3054 5492 95F3 3490 37FF FBBE 5A30 624B B249 <-- That bit is your fingerprint.
|
||||
uid Daniel P. Mahoney <danm@prime.gushi.org>
|
||||
uid Daniel Mahoney (Secondary Email) <gushi@gushi.org>
|
||||
sub 2048g/DE20C529 2000-10-02
|
||||
|
||||
4. Copy the file somewhere, like your webspace. It need not live on the same
|
||||
server. It needs to be accessable by the url you create in the next step.
|
||||
|
||||
%cp 624BB249.pub.asc public_html/danm.pubkey.txt
|
||||
|
||||
5. Make up your text record. The format is:
|
||||
|
||||
danm._pka.prime.gushi.org. TXT "v=pka1;fpr=C2063054549295F3349037FFFBBE5A30624BB249;uri=http://prime.gushi.org/danm.pubkey.txt"
|
||||
|
||||
|
||||
We'll take this in several parts. The record label is simply the email
|
||||
address with "._pka." replacing the "@". danm@prime.gushi.org becomes
|
||||
danm._pka.prime.gushi.org. Don't forget the trailing dot, if you're using the
|
||||
fully qualified name. I recommend sticking with fully-qualified, for
|
||||
simplicity.
|
||||
|
||||
The body of the record is also simple. The v portion is just a version.
|
||||
There's only one version as far as I can tell, 'pka1'. The fpr is the
|
||||
fingerprint, with all whitespace stripped, and in uppercase. The uri is the
|
||||
location a key can be retrieved from. All the "names" are lowercase,
|
||||
separated by semicolons.
|
||||
|
||||
6. Publish the above record in your DNS. Bump your serial number and reload
|
||||
your nameserver. If you're using DNSSEC, re-sign your zone.
|
||||
|
||||
#### Testing
|
||||
|
||||
Most of the tests we're going to do for these are essentially the same
|
||||
activity. See if our DNS server is handing out an answer, and then see if GPG
|
||||
can retrieve it.
|
||||
|
||||
1. A simple dig:
|
||||
|
||||
%dig +short danm._pka.prime.gushi.org. TXT
|
||||
"v=pka1\;fpr=C2063054549295F3349037FFFBBE5A30624BB249\;uri=http://prime.gushi.org/danm.pubkey.txt"
|
||||
|
||||
(The backslashes before the semicolons are normal). Other than that, it seems
|
||||
to make sense and match what I put in.)
|
||||
|
||||
2. Test it with GPG. Rather than messing around with, and adding-from and
|
||||
deleting from live keyrings, you can do:
|
||||
|
||||
%echo "foo" | gpg --no-default-keyring --keyring /tmp/gpg-$$ --encrypt --armor --auto-key-locate pka -r you@you.com
|
||||
|
||||
|
||||
(where you@you.com is the address of your primary key.) The /tmp/gpg-$$
|
||||
creates a random file named after your PID. What you should see, and what I
|
||||
see, is something like this:
|
||||
|
||||
gpg: WARNING: using insecure memory!
|
||||
gpg: please see http://www.gnupg.org/faq.html for more information
|
||||
gpg: keyring `/tmp/gpg-39996' created
|
||||
gpg: requesting key 624BB249 from http server prime.gushi.org
|
||||
gpg: key 624BB249: public key "Daniel P. Mahoney <danm@prime.gushi.org>" imported
|
||||
gpg: public key of ultimately trusted key CF45887D not found
|
||||
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
|
||||
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
|
||||
gpg: Total number processed: 1
|
||||
gpg: imported: 1
|
||||
gpg: automatically retrieved `danm@prime.gushi.org' via PKA
|
||||
gpg: DE20C529: There is no assurance this key belongs to the named user
|
||||
pub 2048g/DE20C529 2000-10-02 Daniel P. Mahoney <danm@prime.gushi.org>
|
||||
Primary key fingerprint: C206 3054 5492 95F3 3490 37FF FBBE 5A30 624B B249
|
||||
Subkey fingerprint: CE40 B786 81E2 5CB9 F7D3 1318 9488 EB58 DE20 C529
|
||||
It is NOT certain that the key belongs to the person named
|
||||
in the user ID. If you *really* know what you are doing,
|
||||
you may answer the next question with yes.
|
||||
Use this key anyway? (y/N) y
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
Version: GnuPG v1.4.10 (FreeBSD)
|
||||
hQIOA5SI61jeIMUpEAf/UotgWP8VQC9VTY36HaZeXO1CTFk90x0qlPrAhJk9YaoA
|
||||
2eHNKZSoHKqaLjzTbaWnWHnNZu0IllIS+qrAwNeIAhswfzDoc8Q9+/4sGSR3LmxA
|
||||
8SEwrJIvLmGVbqJEtnH8TTHIEao/lpL/d+ul4nLfbXRn0NW+MsaCAi8UsjbLlJeV
|
||||
n4p0GQlpDoZCE55DTwMzfWMT84YVwuXTesuN+i7sSyJn2hT1rXuK1BCVcsgTcKdy
|
||||
QhIo3EfKBlfFp74yiU7QCmlAujD6U6a93mmxezPIHVx/WGXgPExVRGgEzfT/tUcI
|
||||
IQ2xMDUv4BF05hgm04GPGCbBY431j4UkdWWI6bvMLwgA2i01NmflH/6Z8+ss6J1M
|
||||
e3RWnR7TPl5lDkXFBtLGAzO+HrsC5A32SbkTw+WsljCQLifJ2EalfoJ1QGY4Sp3v
|
||||
H2YunwZLVPTc+D2JnrXfqNmi5zYZio8by3c8L0CgWdMwZ7PPxZpTOLN77/MIjBkJ
|
||||
EBb8Z6SZCgzTIhN5z56ZgWFvmSKf1vKkeUcrgxMs+DnA+XqBMJ9w520JwoTLjJza
|
||||
syrlYVhd+ktY21DYB9OJ5MZx2HMAtkUDRAzW1zoLcehk1kdZNzhpjU5hqSjT8/GN
|
||||
trKFeqkmKemrq2GvMNyJyrEOB8e7KgbmXa95YKH0Wh2D4SWpXukegyCspmY4tDE+
|
||||
uckaFSao+48g8D6vs1irGSxBRjyhD/jPDblrgpo=
|
||||
=NbgW
|
||||
-----END PGP MESSAGE-----
|
||||
|
||||
The "insecure memory" warning is a silly warning that the only way to turn off is to run GPG setuid root.
|
||||
You can see in the output that the key comes from PKA.
|
||||
|
||||
The "it is NOT certain" warning has nothing to do with the fact that it came
|
||||
from DNS. You will get that warning every time you use that key (or any gpg
|
||||
key) until you have edited it and assigned ownertrust to it, or until the key
|
||||
is signed with a trusted signature, either from your personal web of trust, or
|
||||
from a signing service like the pgp.com directory.
|
||||
|
||||
3. Ask other people to run it for you and send you the resulting blob. You should be able to decrypt it with your private key.
|
||||
|
||||
### PGP CERT Records
|
||||
|
||||
Also known as: The "big" CERT record.
|
||||
|
||||
Relevant RFCs: [RFC 2538](http://www.faqs.org/rfcs/rfc2538.html),
|
||||
[RFC 4398](http://www.faqs.org/rfcs/rfc4398.html), specifically sections 2.1
|
||||
and 3.3
|
||||
|
||||
#### Advantages
|
||||
|
||||
* DNS is all you need. You don't have to host the key elsewhere. As a DNS
|
||||
nerd, this strikes me as very cool.
|
||||
|
||||
* Suprisingly easy to verify with dig, if you have a base64 converter handy
|
||||
(openssl includes one)
|
||||
|
||||
#### Disadvantages
|
||||
|
||||
* These records can get big. Really big. Especially if you have photo-ids on your keys. You can play with export-options to shrink it somewhat. Big dns packets may require EDNS, or dns-over-tcp, which not everyone supports, but support is becoming more widespread as a result of DNSSEC awareness.
|
||||
|
||||
* Requires the make-dns-cert tool, which isn't built by default.
|
||||
|
||||
* Requires you to have some control over your actual zonefile. Most control panels won't cut it.
|
||||
|
||||
* Make-dns-cert currently generates a very ugly record for this.
|
||||
|
||||
#### How to
|
||||
|
||||
1. As before, the first step is to figure out which key we want.
|
||||
|
||||
%gpg --list-keys danm@prime.gushi.org
|
||||
Warning: using insecure memory!
|
||||
pub 1024D/624BB249 2000-10-02 <-- I'm going to use this one.
|
||||
uid Daniel P. Mahoney <danm@prime.gushi.org>
|
||||
uid Daniel Mahoney (Secondary Email) <gushi@gushi.org>
|
||||
sub 2048g/DE20C529 2000-10-02
|
||||
pub 1024R/309C17C5 1997-05-08
|
||||
uid Daniel P. Mahoney <danm@prime.gushi.org>
|
||||
|
||||
2. We export the key, but this time, it needs to be binary.
|
||||
|
||||
%gpg --export 624BB249 > 624BB249.pub.bin
|
||||
Warning: using insecure memory!
|
||||
|
||||
3. We run make-dns-cert on it. make-dns-cert comes with no manual or docs,
|
||||
but running with -h gives you all the clue you need.
|
||||
|
||||
make-dns-cert
|
||||
-f fingerprint
|
||||
-u URL
|
||||
-k key file
|
||||
-n DNS name
|
||||
|
||||
So then,
|
||||
|
||||
make-dns-cert -n danm.prime.gushi.org. -k 624BB249.pub.bin
|
||||
<pre>`%make-dns-cert -n danm.prime.gushi.org. -k 624BB249.pub.bin
|
||||
danm.prime.gushi.org. TYPE37 \# 1298 0003 0000 00 9901A20439D8DAF1110400F770EC6AA006076334BEC6DB6FBB237DC194BC0AB8
|
||||
302C8953F04C28FC2085235D4F10EFA027234FBD63D142CCADD5213AD2B79A22C89ED9B4138370D8220D0F987F993A5364A4A7AC3D42F3765C384
|
||||
71DDD0FF3372E4AE6F7BEE1E18EF464A0BEB5BBE860A08238891455EBE7CB53D567E981F78ADBD263206B0493ADCB74DD00A0FF0E9A1CD245415E
|
||||
CEF59435162AFCE4CDD14BC70400EA38FF501256E773DEA299404854D99F4EDB2757AA911A9C77C68AB8D6622E517A556C43D21F0523C568F016C
|
||||
D0DB89EF435F0D53B4E07434213F899E6578955DC2C147931E7B6901C9FD8A02705417D69A879B3CC196D2AC2EAEF311192EE89ABAF5A60942167
|
||||
B4625735FCBDFB5DE0E3AC1236A53FA4D7CDD7D75F5DE85AF50400867D9546B28B79AF10541053CF4AB06A6171BFD21458BFD12AF1AE2B2401CAD
|
||||
8851661F8AF6602F80EDAC99C79616BE1F910F4156242003779C68D7A079A8B18F89DD293E1B247E7420471300A4A0730AA61DE281CCC211FC405
|
||||
A0A8A79877999FF9042AD892AB927DA371E8883BBB370AB7A97841408C3486BB18598CF2559BB42844616E69656C20502E204D61686F6E6579203
|
||||
C64616E6D407072696D652E67757368692E6F72673E884E04101102000E050239D8DAF1040B030102021901000A0910FBBE5A30624BB249FA2E00
|
||||
9B057503ED498695AE5ED73CA1B98EBAEE13F717E500A0921E0D92724459100266FEBBC29E911C8B0F530BB43244616E69656C204D61686F6E657
|
||||
920285365636F6E6461727920456D61696C29203C67757368694067757368692E6F72673E8860041311020020050245D49FD7021B23060B090807
|
||||
030204150208030416020301021E01021780000A0910FBBE5A30624BB249158400A082C8AF43DA8B85F740D6B1A6E9FF0B4490520B8C00A08F77D
|
||||
21FBF86C842963E8090DC0646D1DD7F95C9B9020D0439D8DAF4100800F64257B7087F081772A2BAD6A942F305E8F95311394FB6F16EB94B3820DA
|
||||
01A756A314E98F4055F3D007C6CB43A994ADF74C648649F80C83BD65E917D4A1D350F8F5595FDC76524F3D3D8DDBCE99E1579259CDFDB8AE744FC
|
||||
5FC76BC83C5473061CE7CC966FF15F9BBFD915EC701AAD35B9E8DA0A5723AD41AF0BF4600582BE5F488FD584E49DBCD20B49DE49107366B336C38
|
||||
0D451D0F7C88B31C7C5B2D8EF6F3C923C043F0A55B188D8EBB558CB85D38D334FD7C175743A31D186CDE33212CB52AFF3CE1B1294018118D7C84A
|
||||
70A72D686C40319C807297ACA950CD9969FABD00A509B0246D3083D66A45D419F9C7CBD894B221926BAABA25EC355E9320B3B00020207FF5E1A3C
|
||||
C5DA00E1E94EC8EF6C7FE9B49D944C71D8BBC817DD8E64A7344B9E48392E0B833B3B1DB7E6D5A38BE2826DEF0060F78C6417871EAF1CFBCBC47D2
|
||||
7E93718D975E0A3A36D868C021D6B771740CE2918307D69D614BBF0632DC31932EA31397A7F3B04618C9A76C2F38265C7037E303EDD8AEF03D069
|
||||
208E3FE9C4EA77D83E6311ED36C013D58C54E914B263A459E22D463A0288510C4752B99C163EEA0A55686979691AB0D9F9AA0C06C834446D7A723
|
||||
EC534D819301382621ACF8930C74E9FD28C8797718AEC2C30CF601E24194B799234104A3D6239657B1D4AD545BDAA637F61541435CB51B4D138FB
|
||||
F55E1A9FD2EED860E4459D6795B6FCCA23155A8846041811020006050239D8DAF4000A0910FBBE5A30624BB249415A009E37BCFDC64E76CBF6A86
|
||||
82B85EA161BD1DFB793DF00A0C471BC7B9723535CD855D8FF1EB93F01E251B698
|
||||
%
|
||||
|
||||
The program prints that all on **one line**.
|
||||
|
||||
Immediately, we notice a few things.
|
||||
|
||||
* The record type isn't "CERT", it's "TYPE37". This confused me for a while until I discovered [RFC3597](http://www.faqs.org/rfcs/rfc3597.html) Basically, it's a way that a DNS server can handle a resource record it doesn't know about, by giving it some special fields like the "#", as well as a length (which is the 1298 you see there).
|
||||
|
||||
* The rest of the record is on one line. I wrapped it for the purposes of brevity. If I were using this in a zonefile, I would need to be careful that I wrapped it on a byte-boundary (every two characters is a byte). If I miss the boundary, named will refuse to load it, dnssec-signzone won't touch it, etc.
|
||||
|
||||
4. So the thing is ugly and you don't want to touch it. The easiest way to work with it is to drop all that into a file:
|
||||
|
||||
%make-dns-cert -n danm.prime.gushi.org. -k 624BB249.pub.bin > 624BB249.big.cert
|
||||
|
||||
|
||||
5. And then either read it into your editor, or tack it on like this:
|
||||
|
||||
%cat 624BB249.big.cert >> your.zonefile
|
||||
|
||||
Be sure to make a backup first. Either way, you never have to copy/paste the raw hex and worry about newlines being inserted where you don't want them.
|
||||
|
||||
6. Before you reload your zone, you might want to use named-checkzone on it first:
|
||||
|
||||
prime# named-checkzone gushi.org gushi.org.hosts
|
||||
zone gushi.org/IN: loaded serial 2009102909
|
||||
OK
|
||||
prime#
|
||||
|
||||
7. Voice of experience: You may want to dial the TTL (which controls how long servers will cache your data) way down on the record above. It's not hard, just put a number before the TYPE37, with a space, i.e:
|
||||
|
||||
danm.prime.gushi.org. 30 TYPE37
|
||||
|
||||
This way if it all goes terribly wrong, or you need to make changes, it won't be cached for very long.
|
||||
|
||||
8. If it looks okay, bump your serial number and reload.
|
||||
|
||||
#### Testing
|
||||
|
||||
1. As above, you can dig, but you won't be able to easily read the results:
|
||||
|
||||
prime# dig +short danm.prime.gushi.org CERT
|
||||
;; Truncated, retrying in TCP mode.
|
||||
|
||||
|
||||
PGP 0 0
|
||||
mQGiBDnY2vERBAD3cOxqoAYHYzS+xttvuyN9wZS8CrgwLIlT8Ewo/CCF
|
||||
I11PEO+gJyNPvWPRQsyt1SE60reaIsie2bQTg3DYIg0PmH+ZOlNkpKes
|
||||
PULzdlw4Rx3dD/M3Lkrm977h4Y70ZKC+tbvoYKCCOIkUVevny1PVZ+mB
|
||||
94rb0mMgawSTrct03QCg/w6aHNJFQV7O9ZQ1Fir85M3RS8cEAOo4/1AS
|
||||
Vudz3qKZQEhU2Z9O2ydXqpEanHfGirjWYi5RelVsQ9IfBSPFaPAWzQ24
|
||||
nvQ18NU7TgdDQhP4meZXiVXcLBR5Mee2kByf2KAnBUF9aah5s8wZbSrC
|
||||
6u8xEZLuiauvWmCUIWe0Ylc1/L37XeDjrBI2pT+k183X119d6Fr1BACG
|
||||
fZVGsot5rxBUEFPPSrBqYXG/0hRYv9Eq8a4rJAHK2IUWYfivZgL4DtrJ
|
||||
nHlha+H5EPQVYkIAN3nGjXoHmosY+J3Sk+GyR+dCBHEwCkoHMKph3igc
|
||||
zCEfxAWgqKeYd5mf+QQq2JKrkn2jceiIO7s3CrepeEFAjDSGuxhZjPJV
|
||||
m7QoRGFuaWVsIFAuIE1haG9uZXkgPGRhbm1AcHJpbWUuZ3VzaGkub3Jn
|
||||
PohOBBARAgAOBQI52NrxBAsDAQICGQEACgkQ+75aMGJLskn6LgCbBXUD
|
||||
7UmGla5e1zyhuY667hP3F+UAoJIeDZJyRFkQAmb+u8KekRyLD1MLtDJE
|
||||
YW5pZWwgTWFob25leSAoU2Vjb25kYXJ5IEVtYWlsKSA8Z3VzaGlAZ3Vz
|
||||
aGkub3JnPohgBBMRAgAgBQJF1J/XAhsjBgsJCAcDAgQVAggDBBYCAwEC
|
||||
HgECF4AACgkQ+75aMGJLskkVhACggsivQ9qLhfdA1rGm6f8LRJBSC4wA
|
||||
oI930h+/hshClj6AkNwGRtHdf5XJuQINBDnY2vQQCAD2Qle3CH8IF3Ki
|
||||
utapQvMF6PlTETlPtvFuuUs4INoBp1ajFOmPQFXz0AfGy0OplK33TGSG
|
||||
SfgMg71l6RfUodNQ+PVZX9x2Uk89PY3bzpnhV5JZzf24rnRPxfx2vIPF
|
||||
RzBhznzJZv8V+bv9kV7HAarTW56NoKVyOtQa8L9GAFgr5fSI/VhOSdvN
|
||||
ILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsYjY67VYy4XTjT
|
||||
NP18F1dDox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM
|
||||
2Zafq9AKUJsCRtMIPWakXUGfnHy9iUsiGSa6q6Jew1XpMgs7AAICB/9e
|
||||
GjzF2gDh6U7I72x/6bSdlExx2LvIF92OZKc0S55IOS4Lgzs7Hbfm1aOL
|
||||
4oJt7wBg94xkF4cerxz7y8R9J+k3GNl14KOjbYaMAh1rdxdAzikYMH1p
|
||||
1hS78GMtwxky6jE5en87BGGMmnbC84JlxwN+MD7diu8D0Gkgjj/pxOp3
|
||||
2D5jEe02wBPVjFTpFLJjpFniLUY6AohRDEdSuZwWPuoKVWhpeWkasNn5
|
||||
qgwGyDREbXpyPsU02BkwE4JiGs+JMMdOn9KMh5dxiuwsMM9gHiQZS3mS
|
||||
NBBKPWI5ZXsdStVFvapjf2FUFDXLUbTROPv1Xhqf0u7YYORFnWeVtvzK
|
||||
IxVaiEYEGBECAAYFAjnY2vQACgkQ+75aMGJLsklBWgCeN7z9xk52y/ao
|
||||
aCuF6hYb0d+3k98AoMRxvHuXI1Nc2FXY/x65PwHiUbaY
|
||||
|
||||
|
||||
It's still ugly, but it's not AS ugly because it's base64, which includes
|
||||
spaces, at least, and is easier to search for a pattern. Base64 can also be
|
||||
easily wrapped on any boundary, which is nice.
|
||||
|
||||
You can run your existing exported key through a base64 converter, like the
|
||||
one built into the openssl binary, if you want to compare:
|
||||
|
||||
%cat 624BB249.pub.bin | openssl enc -base64
|
||||
mQGiBDnY2vERBAD3cOxqoAYHYzS+xttvuyN9wZS8CrgwLIlT8Ewo/CCFI11PEO+g
|
||||
JyNPvWPRQsyt1SE60reaIsie2bQTg3DYIg0PmH+ZOlNkpKesPULzdlw4Rx3dD/M3
|
||||
Lkrm977h4Y70ZKC+tbvoYKCCOIkUVevny1PVZ+mB94rb0mMgawSTrct03QCg/w6a
|
||||
(...etc...)
|
||||
OPv1Xhqf0u7YYORFnWeVtvzKIxVaiEYEGBECAAYFAjnY2vQACgkQ+75aMGJLsklB
|
||||
WgCeN7z9xk52y/aoaCuF6hYb0d+3k98AoMRxvHuXI1Nc2FXY/x65PwHiUbaY
|
||||
|
||||
|
||||
Now, while you could compare things byte-by-byte here, what I've done as a
|
||||
"casual check" is just pick random strings in the text and see if they match
|
||||
up. For example, you can see that "reaIsie2" is present in both. They both
|
||||
start with and end with similar strings on every line. The real test, of
|
||||
course, is to see if GPG recognizes it as a valid key.
|
||||
|
||||
By the way, since I use DNSSEC, dnssec-signzone rewrites this record into the
|
||||
proper "presentation format" for me, which is base64. If you want a similar
|
||||
function, you can use named-compilezone to get some of the same effects, or
|
||||
you can use the shell script I provide later in this document, with which you
|
||||
don't even need make-dns-cert.
|
||||
|
||||
2. Testing with gpg
|
||||
|
||||
As above, the command to test this is remarkably simple:
|
||||
|
||||
%rm /tmp/gpg-*
|
||||
%echo "foo" | gpg --no-default-keyring --keyring /tmp/gpg-$$ --encrypt --armor --auto-key-locate cert -r danm@prime.gushi.org
|
||||
gpg: keyring `/tmp/gpg-39996' created
|
||||
gpg: key 624BB249: public key "Daniel P. Mahoney <danm@prime.gushi.org>" imported
|
||||
gpg: Total number processed: 1
|
||||
gpg: imported: 1
|
||||
gpg: automatically retrieved `danm@prime.gushi.org' via DNS CERT
|
||||
gpg: DE20C529: There is no assurance this key belongs to the named user
|
||||
pub 2048g/DE20C529 2000-10-02 Daniel P. Mahoney <danm@prime.gushi.org>
|
||||
Primary key fingerprint: C206 3054 5492 95F3 3490 37FF FBBE 5A30 624B B249
|
||||
Subkey fingerprint: CE40 B786 81E2 5CB9 F7D3 1318 9488 EB58 DE20 C529
|
||||
It is NOT certain that the key belongs to the person named
|
||||
in the user ID. If you *really* know what you are doing,
|
||||
you may answer the next question with yes.
|
||||
Use this key anyway? (y/N) y
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
Version: GnuPG v1.4.10 (FreeBSD)
|
||||
hQIOA5SI61jeIMUpEAf/Sx7MKWm+e9EpUTSrDaBp4nJfDcBeqbYJulPRbDZz7eVW
|
||||
2+ol6sG0jWjuirbG1YppZccEr9mgqaQujdSXb/bleD8POS0TEWuf3aPswFQvHf90
|
||||
NLEzHt6BnfLoeobXXxyCflNaGX8zW+XgJtwZqAc2+jietuz8MOUhrf5m17CsW/wZ
|
||||
IuEqwaek+K1irJp+w3rhaE08Jzb/S4CCifeW9J3mK57chQoPOu7Nz3rY666YKp/3
|
||||
9T9StOgmFiNpvtFPNy4N7hHMHvbQwRsKlnkl+a7n0Aq2+OF4d1+/k2EE4uSGgcz0
|
||||
oHvee8DnuOx3P92mO4Jz5/0O0lwBD7I51iOjzUurTAgAiIM5sHV8/QFCVzH9Ule+
|
||||
gd8Wo5momcphkU/AXpce5Xgi/Vm4oGQ0x0queii8afUrzkpeN5SuwgQfAdOPiXW5
|
||||
2bo527jBllxOxjeBasfky82XheTnLzbAQNvQNTEM9zE7zCl1LQJUZEJ1hVzcOevI
|
||||
s+cm/AaGII9VkrAtSt3aLSRZuRJHFmhGvYd2Hz5WzcV1YFjXXP1eLwfetDBlaeB9
|
||||
/K5v4hZBkIZPbHX0DcLVrP96mCIT4wCBYSJw+I6n0E6Fz3IfybQG2HMfqWp966/c
|
||||
00ijx/aRDh42Dr/fTropuzzFzQr7weYDa1JnN3Zoftv6Zb/n+NcrmMiDCH8jJV6E
|
||||
uMkaeeB5Mv7ssDQ9kPhO989CHFcznrE1lgOxjX8=
|
||||
=NTLY
|
||||
-----END PGP MESSAGE-----
|
||||
|
||||
Okay, as above, try to decrypt that with your private key.
|
||||
|
||||
### IPGP CERT Records
|
||||
|
||||
Also known as: The "little" or "short" CERT record. (These terms are purely my
|
||||
own).
|
||||
|
||||
Relevant RFCs: [RFC 2538](http://www.faqs.org/rfcs/rfc2538.html),
|
||||
[RFC 4398](http://www.faqs.org/rfcs/rfc4398.html), specifically sections 2.1
|
||||
and 3.3
|
||||
|
||||
IPGP certs are interesting. It's basically the same pieces of infomation that
|
||||
are in the PKA record, as above, except that it's supported by an RFC.
|
||||
Despite the RFC compliance, I am not sure if any non-gpg client knows to look
|
||||
for them. However, because it's a DNS cert, make-dns-cert encodes the
|
||||
information in binary, and your DNS server will see it in base64. So
|
||||
verifying it visually is harder than verifying either of the above.
|
||||
|
||||
#### Advantages
|
||||
|
||||
* Small, easy-to-transmit records.
|
||||
* Can use the same uri as the PKA record.
|
||||
|
||||
#### Disadvantages
|
||||
|
||||
* Relies on the URI scheme. I haven't yet been able to get a definitive list
|
||||
of what uri schemes are supported, although I've seen http and finger. I've
|
||||
also seen reports that unless gpg is compiled against curl, http 1.1 is not
|
||||
supported (what this actually means is that any host that supports SSL will
|
||||
probably work, because of some of the nuances of SSL).
|
||||
* With PGP certs and IPGP certs, GPG will only parse the first key it gets, so
|
||||
if you publish both, and one doesn't work, there's no failover. I've argued
|
||||
that this should be fixed.
|
||||
* Requires make-dns-cert, which is not built in GPG by default. (But see "A
|
||||
Better Way" below)
|
||||
* Requires publication in your main DNS zone.
|
||||
* Despite being RFC compliant, GPG has additional trust vectors for PKA but
|
||||
not this, despite the fact that they share basically the same information.
|
||||
* Harder to verify with dig.
|
||||
|
||||
#### Howto
|
||||
|
||||
1. Note that some of these steps are redundant. If you're already doing a PKA
|
||||
key, skip to step 5.
|
||||
|
||||
2. Dig:
|
||||
|
||||
%gpg --list-keys danm@prime.gushi.org
|
||||
Warning: using insecure memory!
|
||||
pub 1024D/624BB249 2000-10-02 <-- I'm going to use this one.
|
||||
uid Daniel P. Mahoney <danm@prime.gushi.org>
|
||||
uid Daniel Mahoney (Secondary Email) <gushi@gushi.org>
|
||||
sub 2048g/DE20C529 2000-10-02
|
||||
pub 1024R/309C17C5 1997-05-08
|
||||
uid Daniel P. Mahoney <danm@prime.gushi.org>
|
||||
|
||||
3. Export the key to a file (I use keyid.pub.asc, but it can be anything)
|
||||
|
||||
%gpg --export --armor 624BB249 > 624BB249.pub.asc
|
||||
Warning: using insecure memory!
|
||||
%
|
||||
|
||||
4. Get the fingerprint for your key:
|
||||
|
||||
%gpg --list-keys --fingerprint 624BB249
|
||||
gpg: WARNING: using insecure memory!
|
||||
gpg: please see http://www.gnupg.org/faq.html for more information
|
||||
pub 1024D/624BB249 2000-10-02
|
||||
Key fingerprint = C206 3054 5492 95F3 3490 37FF FBBE 5A30 624B B249 <-- That bit is your fingerprint.
|
||||
uid Daniel P. Mahoney <danm@prime.gushi.org>
|
||||
uid Daniel Mahoney (Secondary Email) <gushi@gushi.org>
|
||||
sub 2048g/DE20C529 2000-10-02
|
||||
|
||||
5. As above, run make-dns-cert. This time we use the -n, -f, and -u options:
|
||||
|
||||
%make-dns-cert -n danm.prime.gushi.org. -f C2063054549295F3349037FFFBBE5A30624BB249 -u http://prime.gushi.org/danm.pubkey.txt
|
||||
danm.prime.gushi.org. TYPE37 \# 64 0006 0000 00 14 C2063054549295F3349037FFFBBE5A30624BB249 687474703A2F2F7072696D652E67757368692E6F72672F64616E6D2E7075626B65792E747874
|
||||
%
|
||||
|
||||
|
||||
6. Put the above in DNS. All on one line. Optionally add a TTL.
|
||||
|
||||
7. IMPORTANT: make sure you don't have any other CERT records with the same
|
||||
label (i.e. a "big" cert, as above). While it won't break things, you have
|
||||
no control over which (of multiple) people will get.
|
||||
|
||||
8. Reload your zone, and test. Testing will probably look VERY MUCH like the
|
||||
above, but here are the steps anyway:
|
||||
|
||||
#### Testing
|
||||
|
||||
1. Dig:
|
||||
|
||||
%dig +short danm.prime.gushi.org CERT
|
||||
6 0 0 FMIGMFRUkpXzNJA3//u+WjBiS7JJaHR0cDovL3ByaW1lLmd1c2hpLm9y Zy9kYW5tLnB1YmtleS50eHQ=
|
||||
|
||||
Sadly, I haven't come across an easy way to decipher it yet, but there's
|
||||
always gpg.
|
||||
|
||||
2. GPG:
|
||||
|
||||
Since we're fetching the same kind of record, the command is exactly the same
|
||||
as before:
|
||||
|
||||
%echo "foo" | gpg --no-default-keyring --keyring /tmp/gpg-$$ --encrypt --armor --auto-key-locate cert -r danm@prime.gushi.org
|
||||
gpg: WARNING: using insecure memory!
|
||||
gpg: please see http://www.gnupg.org/faq.html for more information
|
||||
gpg: keyring `/tmp/gpg-39996' created
|
||||
gpg: requesting key 624BB249 from http server prime.gushi.org
|
||||
gpg: key 624BB249: public key "Daniel P. Mahoney <danm@prime.gushi.org>" imported
|
||||
gpg: public key of ultimately trusted key CF45887D not found
|
||||
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
|
||||
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
|
||||
gpg: Total number processed: 1
|
||||
gpg: imported: 1
|
||||
gpg: automatically retrieved `danm@prime.gushi.org' via DNS CERT
|
||||
gpg: DE20C529: There is no assurance this key belongs to the named user
|
||||
pub 2048g/DE20C529 2000-10-02 Daniel P. Mahoney <danm@prime.gushi.org>
|
||||
Primary key fingerprint: C206 3054 5492 95F3 3490 37FF FBBE 5A30 624B B249
|
||||
Subkey fingerprint: CE40 B786 81E2 5CB9 F7D3 1318 9488 EB58 DE20 C529
|
||||
It is NOT certain that the key belongs to the person named
|
||||
in the user ID. If you *really* know what you are doing,
|
||||
you may answer the next question with yes.
|
||||
Use this key anyway? (y/N) y
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
Version: GnuPG v1.4.10 (FreeBSD)
|
||||
hQIOA5SI61jeIMUpEAgApZurJi3hZmDaUFjB2j93eX/lTl96xq6T//sz6nT6jcTx
|
||||
IPnq1RN8IrIQPjDBByHdqOZBT5hhblr9xi7NKIIv3W4q4L0z0fJx7NERPZNvn/H0
|
||||
DkTwfDgAvCRxcKjenpLSwKZFwLjyfS7wjlDr3HFX7Tila0hbzplHslvgTE0QMcd7
|
||||
7oNmEyOL3z+yZr/afQGp2wpzDv4YB9zOiNHcHcenqX0yrtiqKozZ9VAldi53rb/q
|
||||
f38lwInbveyAcEQkE2iFwhRsbMR4VLcsBoxY6D9brsBprt23ey8Rnv+bQ9IAR0VN
|
||||
/WYzU4zUUqb8HmpNFXQLEgH8A2BENw+bxkVYHjSfWQf/cBSGAzfBQQVJ7qp4tN0Z
|
||||
FRVe51dokbU4NM9tGBdCzFHWARVkQX/Ulekd4F3sxBR/sum1UOT2xl2THVBz7/Pq
|
||||
UCrTRPA0uH4dIbL5JpfGZhqsJ079+wmUWUtJIiO2wXi7ePEA/DrBC6p7jlmjyYN/
|
||||
AeSKcPoTeLX+zryV5bECx4RO6S56EEcy0Ns0pASGMsgUnKL6Adrv3Y6ea3ZAOQMn
|
||||
H9Uo28BKTKNUvUaBpN8cV8jIbKYPPW9i04kvEQRqs5rdamERCY1vVTqYTrcLsNqz
|
||||
fF3KopX+V82X1oE2QuGdFfd8mK57ZXJL3VRUrfohQjhfYNKzougiP46rQQv79MYT
|
||||
j8kazWyJUuufm6NVco1/35Zdp1UhHu8qTgXxrjo=
|
||||
=zY9G
|
||||
-----END PGP MESSAGE-----
|
||||
%
|
||||
|
||||
Strangely, the output doesn't say what PKA does (a PKA retrieval has a line
|
||||
about fetching via HTTP), however, by checking my webserver logs, I can see it
|
||||
retrieved it from there:
|
||||
|
||||
%tail -200 /usr/local/apache/logs/prime.gushi.org.log | grep pubkey | tail -1
|
||||
prime.gushi.org 72.9.101.130 - - [28/Oct/2009:23:50:43 -0400] "GET /danm.pubkey.txt HTTP/1.1" 200 4337 "-" "-"
|
||||
%
|
||||
|
||||
As usual, test decryption, etc. You're done.
|
||||
|
||||
## Further Steps
|
||||
|
||||
* Figure out which of these are useful to you, and use them.* When someone
|
||||
asks for your public key, tell them to run the above command instead of
|
||||
mailing them your key or sending them a keyserver URL.
|
||||
|
||||
* Consider using the pka-related verify-options.
|
||||
|
||||
* Look into embracing DNSSEC. With a signed root, there's a good trust-path
|
||||
vector here. Who knows, maybe some day GPG will be dnssec-aware so it will
|
||||
give more credit to a secure DNS transaction. Without a signed root, there
|
||||
are still ways to have those who care about security use it, through
|
||||
services such as [ISC's DLV registry](http://dlv.isc.org).
|
||||
|
||||
* On DNSSEC: At present, GPG cannot see the difference between an insecure
|
||||
response (one from an unsigned zone) and a correctly validated one from a
|
||||
signed zone. (In a signed zone, an unsigned or malformed will simply get a
|
||||
SERVFAIL dns response). Look into sponsoring development of GPG to make it
|
||||
as an application more aware of this.
|
||||
|
||||
## A better way to generate records
|
||||
|
||||
In reading over a lot of these commands, I've come across a few problems with
|
||||
the tools involved. They either require you to assemble large records by
|
||||
hand, or manipulate huge files.
|
||||
|
||||
DNS has also come a long way since these tools were written, and RFCs have
|
||||
solidified that have determined the "presentation format" (i.e. the "master
|
||||
file format") of what CERT records should look like.
|
||||
|
||||
On top of everything, the make-dns-cert tool is not built by default, and is
|
||||
not present in most binary distributions (RPM's, deb packages, FreeBSD's
|
||||
ports).
|
||||
|
||||
Thus, I took it upon myself to rewrite make-dns-cert as a shell script.
|
||||
|
||||
### Advantages
|
||||
|
||||
* Extracts your key for you (takes a keyid as the argument).
|
||||
* Formats all three record types for you, you can pipe it right into your zone
|
||||
file.
|
||||
* Takes email address as an argument, generates record label.
|
||||
* No compiling needed.
|
||||
* Should work with most systems. Requires openssl and sed, a few other
|
||||
standard utilities.
|
||||
* Generates base64-ified CERT records, split into easy, manageable pieces.
|
||||
* Generates DNS-friendly comments, so repeating tasks are easy to reference.
|
||||
* (Eventually) available as a tarball, or as a paste-and-go script.
|
||||
* Arguments are in logical DNS record order `emailaddress keyid [url]`.
|
||||
* Will generate an IPGP CERT record without a URI (this is legal per RFC4398).
|
||||
|
||||
You can see sample output
|
||||
[here](http://www.gushi.org/make-dns-cert/sample-output.txt), and you can view
|
||||
the script itself
|
||||
[here](http://www.gushi.org/make-dns-cert/make-dns-cert.sh.txt). Depending on
|
||||
your MIME settings, you can probably get a download link if you go
|
||||
[here](http://www.gushi.org/make-dns-cert/make-dns-cert.sh). If you see the
|
||||
script rather than getting a download prompt, you can just save-as.
|
||||
|
||||
README, Changelog, TODO coming soon.
|
||||
|
||||
## Other notes
|
||||
|
||||
I'm not 100 percent sure (mainly because I haven't tried), but with IPGP cert,
|
||||
and PKA, I believe I could in theory point at a keyserver directly, for
|
||||
example, specify a uri of
|
||||
[http://pgp.mit.edu:11371/pks/lookup?op=get&search=0xB0307039309C17C5](http://pgp.mit.edu:11371/pks/lookup?op=get&search=0xB0307039309C17C5).
|
||||
I'm a bit dubious about the question marks and equals-signs, or if I might
|
||||
have to uri-encode things. It's something to be tried.
|
||||
|
||||
I'm trying to convince the GPG people that this would be much better adopted
|
||||
if the make-dns-cert tool was built/included by default, or if its function
|
||||
were included in gpg rather than a third-party tool. This is analagous as to
|
||||
how dnssec-keygen is used to generate SSHFP DNS records.
|
||||
|
||||
It doesn't do any actual cryptography, just some binary conversion, so in
|
||||
theory it could be rewritten in pure-perl, so there's nothing to compile.
|
||||
|
||||
I've made the argument to the GPG developers that if multiple CERT records are
|
||||
available, all should be tried if one fails. So far, if multiple exist, only
|
||||
the first received is parsed, and of course, DNS round-robins the answers by
|
||||
default.
|
||||
|
||||
It took me quite a lot of trial and error to realize that there's a difference
|
||||
between "modern" RSA keys, like this:
|
||||
|
||||
%gpg --list-keys --fingerprint gushi@prime.gushi.org
|
||||
pub 2048R/CF45887D 2009-10-29
|
||||
Key fingerprint = FCB0 485E 050D DDFA 83C6 76E3 E722 3C05 CF45 887D
|
||||
uid Gushi Test <gushi@prime.gushi.org>
|
||||
sub 2048R/C9761244 2009-10-29
|
||||
|
||||
and ancient RSA keys like this pgp2.6.2 monster:
|
||||
|
||||
%gpg --list-keys --fingerprint danm@prime.gushi.org
|
||||
pub 1024R/309C17C5 1997-05-08
|
||||
Key fingerprint = 04 4B 1A 2E C4 62 95 73 73 A4 EA D0 08 A4 45 76
|
||||
uid Daniel P. Mahoney <danm@prime.gushi.org>
|
||||
|
||||
Note the lack of a subkey there. Note the weird fingerprint. I have not been
|
||||
able to get this key to properly export with gpg. If someone knows the Deep
|
||||
Magic, let me know.
|
||||
|
||||
## References
|
||||
|
||||
### Blog posts and list threads
|
||||
|
||||
While researching this I came across little more than a few blog posts, and a
|
||||
few short discussions on the gpg-devel mailing list.
|
||||
|
||||
* [A blog entry](http://www.df7cb.de/blog/2007/openpgp-dns.html) that seems to
|
||||
have things mostly right.
|
||||
|
||||
* [GPG Mailing List Discussion](http://lists.gnupg.org/pipermail/gnupg-users/2006-April/028314.html)
|
||||
which seems to date towhen these features were first added.
|
||||
|
||||
* [My own thread](http://www.mail-archive.com/gnupg-users@gnupg.org/msg12336.html)
|
||||
on the gnupg-users mailing list that led upto this doc.
|
||||
|
||||
* [A slideshow of a talk given on PKA](ftp://ftp.g10code.com/people/werner/talks/pka-intro.ps.gz)
|
||||
(really the only doc I couldfind with regard to PKA). Note that this is a
|
||||
postscript doc, for reasons I cannot fathom.
|
||||
|
||||
### RFCs
|
||||
|
||||
* [RFC 3597](http://www.faqs.org/rfcs/rfc3597.html) defines the odd format of
|
||||
the records that make-dns-cert generates, if itconfuses you.
|
||||
|
||||
* [RFC 2538](http://www.faqs.org/rfcs/rfc2538.html), which was superseded by
|
||||
[RFC4398](http://www.faqs.org/rfcs/rfc4398.html), defines the format for a
|
||||
CERT record.
|
||||
|
||||
## Todo
|
||||
|
||||
* At least one GPG enthusiast has suggested to me that any tools I write to
|
||||
handle keys should simply be able to insert themusing nsupdate. I don't
|
||||
disagree, but there's a complicated metric there as some of these require
|
||||
manipulation of a site'smain zone, or at the very least, many subzones. In
|
||||
doing this I'd also like to find out a bit about how to do nsupdate
|
||||
withsig(0) and KEY records, which with the right policies would mean I could
|
||||
do this without touching named.conf. That may be the subject of a whole
|
||||
other howto.
|
||||
|
||||
* (Done) I need to get the shell script cleaned up a bit more, and generate
|
||||
proper docs, and start tracking it with version control.
|
||||
|
||||
* I should probably get the gumption up to formally license all this stuff.
|
||||
For right now, I declare it under the
|
||||
[ISCLicense](http://en.wikipedia.org/wiki/ISC_license).
|
||||
|
||||
* I'd like to track down the full list of supported URI types for PKA/IPGP
|
||||
CERT records. There doesn't seem to be a defined standard for it.
|
||||
|
||||
## Epilogue
|
||||
|
||||
### About the author
|
||||
|
||||
Dan Mahoney is a Systems Admin in the Bay Area, California. In his spare time
|
||||
he enjoys thinking for those brief fleeting moments what he would do if he had
|
||||
more free time. Keyid 624BB249, or email address danm@prime.gushi.org.
|
||||
|
||||
### About this Document
|
||||
|
||||
This document was written in [gnu nano](http://nano-editor.org), and HTML was
|
||||
generated using [Markdown](http://daringfireball.net/projects/markdown).
|
||||
|
||||
Markdown rocks.
|
||||
|
||||
Originally published on my livejournal at
|
||||
[http://gushi.livejournal.com/524199.html](http://gushi.livejournal.com/524199.html),
|
||||
its main home is at
|
||||
[http://www.gushi.org/make-dns-cert/HOWTO.html](http://www.gushi.org/make-dns-cert/HOWTO.html),
|
||||
which is where later versions will be published.
|
||||
|
||||
Free to use, comments to the above email address are welcome.
|
|
@ -0,0 +1,133 @@
|
|||
[Christoph Berg's Blog](../index.html)/
|
||||
|
||||
[2007](../2007.html)/
|
||||
|
||||
</span>
|
||||
<span class="title">
|
||||
OpenPGP keys in DNS
|
||||
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
|
||||
* [RecentChanges](../recentchanges.html)
|
||||
* [History](http://svn.df7cb.de/viewcvs.cgi/trunk/2007/openpgp-dns.mdwn?root=blog&view=log)
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="pagebody">
|
||||
|
||||
<div id="content">
|
||||
|
||||
The latest addition to the mutt CVS tree is PKA support via gpgme. While trying
|
||||
to figure out how that works in mutt (I haven't yet...) I configured my DNS
|
||||
server for PKA and CERT records.
|
||||
|
||||
## PKA
|
||||
|
||||
PKA (public key association) puts a pointer where to obtain a key into a TXT
|
||||
record. At the same time that can be used to verify that a key belongs to a
|
||||
mail address. The documentation is at the
|
||||
[g10code website](http://www.g10code.de/docs/pka-intro.de.pdf)
|
||||
(only in German so far). I put the following into the df7cb.de zone:
|
||||
|
||||
<p>
|
||||
cb._pka IN TXT "v=pka1;fpr=D224C8B07E63A6946DA32E07C5AF774A58510B5A;uri=finger:cb@df7cb.de"
|
||||
|
||||
<pre>
|
||||
$ host -t TXT cb._pka.df7cb.de
|
||||
cb._pka.df7cb.de descriptive text "v=pka1\;fpr=D224C8B07E63A6946DA32E07C5AF774A58510B5A\;uri=finger:cb@df7cb.de"
|
||||
</pre>
|
||||
|
||||
Now gpg can be told to use PKA to find the key:
|
||||
|
||||
<pre>
|
||||
$ echo foo | gpg --auto-key-locate pka --recipient cb@df7cb.de --encrypt -a
|
||||
gpg: no keyserver known (use option --keyserver)
|
||||
gpg: requesting key 58510B5A from finger:cb@df7cb.de
|
||||
gpg: key 58510B5A: public key "Christoph Berg " imported
|
||||
gpg: Total number processed: 1
|
||||
gpg: imported: 1
|
||||
gpg: automatically retrieved `cb@df7cb.de' via PKA
|
||||
</pre>
|
||||
|
||||
## CERT
|
||||
|
||||
CERT records work similarly. Records are generated by make-dns-cert (from the
|
||||
tools directory in the gnupg source). cb.gpg is a stripped-down gpg keyring
|
||||
(created with pgp-clean -s and converting from .asc to .gpg).
|
||||
|
||||
<pre>
|
||||
$ ./make-dns-cert -f D224C8B07E63A6946DA32E07C5AF774A58510B5A -n cb
|
||||
cb TYPE37 \# 26 0006 0000 00 14 D224C8B07E63A6946DA32E07C5AF774A58510B5A
|
||||
$ ./make-dns-cert -k cb.gpg -n cb
|
||||
cb TYPE37 \# 1338 0003 0000 00 9901A20440 [...] 509C96D4BFF17B7
|
||||
</pre>
|
||||
|
||||
With a new bind and host (backports.org!) the format looks a bit nicer, that's
|
||||
also what I copied into the zone file:
|
||||
|
||||
<pre>
|
||||
$ host -t CERT cb.df7cb.de
|
||||
;; Truncated, retrying in TCP mode.
|
||||
cb.df7cb.de has CERT record PGP 0 0 mQGiBECBGdAR [...] UDlCcltS/8Xtw==
|
||||
cb.df7cb.de has CERT record 6 0 0 FNIkyLB+Y6aUbaMuB8Wvd0pYUQta
|
||||
</pre>
|
||||
|
||||
Again, gpg can be told to use that:
|
||||
|
||||
<pre>
|
||||
$ echo foo | gpg --auto-key-locate cert --recipient cb@df7cb.de --encrypt -a
|
||||
gpg: key 58510B5A: public key "Christoph Berg " imported
|
||||
gpg: Total number processed: 1
|
||||
gpg: imported: 1
|
||||
gpg: automatically retrieved `cb@df7cb.de' via DNS CERT
|
||||
</pre>
|
||||
|
||||
Thanks to weasel for some hints on using CERT.
|
||||
|
||||
## SSHFP
|
||||
|
||||
I'm also mentioning SSHFP records here since it fits in the topic - I have been
|
||||
using them for some months now:
|
||||
|
||||
<pre>
|
||||
$ host -t SSHFP tesla.df7cb.de
|
||||
tesla.df7cb.de has SSHFP record 1 1 EE49B803541293656C33B86ECD781BD8F1D78AB5
|
||||
tesla.df7cb.de has SSHFP record 2 1 3E82FB5EE8AA0205305F0D0186F94D6FB3E0E744
|
||||
$ ssh -o 'VerifyHostKeyDNS yes' tesla.df7cb.de
|
||||
The authenticity of host 'tesla.df7cb.de (88.198.227.218)' can't be established.
|
||||
RSA key fingerprint is 5a:c9:38:ca:c0:2b:11:c1:c8:fb:f1:ad:73:a1:9c:8b.
|
||||
Matching host key fingerprint found in DNS.
|
||||
Are you sure you want to continue connecting (yes/no)?
|
||||
</pre>
|
||||
|
||||
The records are generated with ssh-keygen -r.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="footer" class="pagefooter">
|
||||
|
||||
<div id="pageinfo">
|
||||
|
||||
<div class="tags">
|
||||
Tags:
|
||||
|
||||
[debian](../tag/debian.html)
|
||||
|
||||
</div>
|
||||
|
||||
<div class="pagedate">
|
||||
Last edited <span class="date">Do 17 Feb 2011 13:21:52 CET</span>
|
||||
<!-- Created <span class="date">Do 01 Mär 2007 20:01:27 CET</span> -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- from Christoph Berg's Blog -->
|
|
@ -0,0 +1,208 @@
|
|||
# taken from http://atom.smasher.org/gpg/gpg-migrate.txt on 8 Aug 2013
|
||||
|
||||
-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA1
|
||||
|
||||
HOW TO MIGRATE A (SUB)KEY INTO A NEW KEY
|
||||
|
||||
this document [is intended to] explain how to migrate a key or subkey from
|
||||
one OpenPGP key into another OpenPGP key.
|
||||
|
||||
here i'll walk you through the steps of migrating my old primary signing
|
||||
key (3D7D41E3) into my new key (D9F57808). the process of migrating
|
||||
encryption (and signing) subkeys is nearly identical. for the adventurous,
|
||||
you can even migrate several keys at once using this method (i recommend
|
||||
going through it once or twice with only one key).
|
||||
|
||||
============================================================================
|
||||
|
||||
doing this requires:
|
||||
1) basic knowledge of a *nix command line and how to use it
|
||||
2) advanced knowledge of gpg and how to use it
|
||||
3) common sense (BACKUP YOUR DATA!!)
|
||||
|
||||
please note:
|
||||
* this works for me. that does not necessarily mean that it will
|
||||
work for you
|
||||
* if you screw something up, it's *YOUR* problem, not mine
|
||||
* this was tested with GnuPG 1.2.4, and written on or about
|
||||
12 May 2004
|
||||
* updates, if there are any, will probably be noted above
|
||||
* comments and suggestions about this tutorial should be sent to:
|
||||
<atom {at} smasher.org>
|
||||
* questions about gpg should be sent to the gnupg-users mailing
|
||||
list: http://lists.gnupg.org/mailman/listinfo/gnupg-users
|
||||
|
||||
============================================================================
|
||||
|
||||
* old key: 3EBE 2810 30AE 601D 54B2 4A90 9C28 0BBF 3D7D 41E3
|
||||
|
||||
pub 1024D/3D7D41E3 2003-10-04 Atom Smasher <atom@suspicious.org>
|
||||
uid Atom Smasher <atom@smasher.org>
|
||||
sub 2048g/1E88BF71 2003-10-04 [expires: 2005-01-26]
|
||||
|
||||
================
|
||||
|
||||
* new key: 762A 3B98 A3C3 96C9 C6B7 582A B88D 52E4 D9F5 7808
|
||||
|
||||
pub 4096R/D9F57808 2004-05-11 Atom Smasher <atom@smasher.org>
|
||||
uid Atom Smasher <atom@suspicious.org>
|
||||
sub 1024D/3D7D41E3 2003-10-04 [expires: 2006-02-13]
|
||||
sub 2048g/1E88BF71 2003-10-04 [expires: 2006-01-26]
|
||||
|
||||
===================================
|
||||
|
||||
backup the new keys:
|
||||
$ gpg --export D9F57808 > D9F57808_original.txt
|
||||
$ gpg --export-secret-key D9F57808 > D9F57808_original_secret.txt
|
||||
|
||||
and the old keys:
|
||||
$ gpg --export 3D7D41E3 > 3D7D41E3_original.txt
|
||||
$ gpg --export-secret-key 3D7D41E3 > 3D7D41E3_original_secret.txt
|
||||
|
||||
break the old secret key into pieces:
|
||||
$ gpg --export-secret-key 3D7D41E3 | gpgsplit -vp OLD_SEC
|
||||
|
||||
===================================
|
||||
|
||||
in this case, the old primary key needs to be converted into a subkey.
|
||||
pgpdump shows that it's a primary key:
|
||||
$ pgpdump OLD_SEC000001-005.secret_key
|
||||
Old: Secret Key Packet(tag 5)
|
||||
<<snip>>
|
||||
|
||||
only do this if you're converting a PRIMARY KEY into a SUBKEY: open that
|
||||
file in a hex editor and refer to RFC2440 4.2 & 4.3. i recommend setting
|
||||
the hex editor into a binary display. in this example the first byte is
|
||||
"10010101" and it needs to be changed to "10011101". the change can be
|
||||
confirmed with pgpdump:
|
||||
$ pgpdump OLD_SEC000001-005.secret_key
|
||||
Old: Secret Subkey Packet(tag 7)
|
||||
<<snip>>
|
||||
|
||||
we've now converted the old primary key into a subkey. if you're moving a
|
||||
subkey from one key to another, you don't have to do that.
|
||||
|
||||
===================================
|
||||
|
||||
use "edit-key" and add a subkey of the same type (DSA, for this example)
|
||||
and size to the new key. this subkey will be discarded, but we need to
|
||||
generate it for now: this seems to be the quickest way to generate a
|
||||
keybinding signature with the correct features.
|
||||
|
||||
exit from "edit-key" and save.
|
||||
|
||||
===================================
|
||||
|
||||
split the current version of the new public key:
|
||||
$ gpg --export D9F57808 | gpgsplit -vp TEMP_KEY1
|
||||
the only part we need from that split is the binding signature that was
|
||||
just generated.
|
||||
|
||||
delete (from the keyring) both the private and public copies of the new
|
||||
key:
|
||||
$ gpg --delete-secret-key D9F57808
|
||||
$ gpg --delete-key D9F57808
|
||||
|
||||
also delete (from the keyring) both the private and public copies of the
|
||||
old key.
|
||||
|
||||
because the subkey that we're adding to the new key does not correspond to
|
||||
the subkey binding signature that was created for it, gpg will not allow
|
||||
the key to be imported. the way around that is to "force feed" the key into
|
||||
the keyring, bypassing the normal sanity checks. once it's in the keyring
|
||||
we can make it all work.
|
||||
|
||||
* note: the actual file names that you're using may differ somewhat from
|
||||
mine. when in doubt (or rather, when you're not sure), use pgpdump to
|
||||
examine the contents of files.
|
||||
|
||||
~import~ (directly into the secret keyring) the original copy of the new
|
||||
key (D9F57808_original_secret.txt), the edited copy of the old primary key
|
||||
(now a subkey, OLD_SEC000001-005.secret_key) and the binding signature of
|
||||
the subkey that we just generated (TEMP_KEY1000007-002.sig):
|
||||
$ cat D9F57808_original_secret.txt \
|
||||
OLD_SEC000001-005.secret_key \
|
||||
TEMP_KEY1000007-002.sig >> ~/.gnupg/secring.gpg
|
||||
|
||||
~import~ (directly into the public keyring) a public key by adding
|
||||
"| gpgsplit --no-split --secret-to-public" to the above command like this:
|
||||
$ cat D9F57808_original_secret.txt \
|
||||
OLD_SEC000001-005.secret_key \
|
||||
TEMP_KEY1000007-002.sig \
|
||||
| gpgsplit --no-split --secret-to-public >> ~/.gnupg/pubring.gpg
|
||||
|
||||
now we have to make a *valid* keybinding signature for the subkey that we
|
||||
just added. use "edit-key", select the newly added subkey, and reset it's
|
||||
expiration date. that will generate a valid keybinding signature.
|
||||
|
||||
while in "edit-key", reset the password. otherwise you may inadvertently
|
||||
create a key with multiple passwords, as described here -
|
||||
http://atom.smasher.org/gpg/gpg-passwords.txt
|
||||
|
||||
exit from "edit-key" and save.
|
||||
|
||||
=========================
|
||||
|
||||
this last part makes no sense to me (but it doesn't seem to work
|
||||
otherwise).
|
||||
|
||||
back up (export) the latest version of the public and private keys:
|
||||
$ gpg --export-secret-key D9F57808 > new-key.sec
|
||||
$ gpg --export D9F57808 > new-key.pub
|
||||
|
||||
delete (from the keyring) the private and public key:
|
||||
$ gpg --delete-secret-key D9F57808
|
||||
$ gpg --delete-key D9F57808
|
||||
|
||||
also, delete (from the keyring) all copies of the old (sub)key that was
|
||||
just added to the new key.
|
||||
|
||||
import the new public and private keys:
|
||||
$ gpg --import new-key.sec new-key.pub
|
||||
|
||||
=========================
|
||||
|
||||
before you publish your new key:
|
||||
|
||||
* make sure the key is "ultimately trusted". deleting and importing will
|
||||
have removed it from the trust db. since you own the key, ultimate trust
|
||||
seems reasonable.
|
||||
|
||||
* check all expiration dates and preferences. some of these operations may
|
||||
have changed your expiration dates and preferences; reset as necessary.
|
||||
|
||||
* test out all key components for creating and verifying signatures, and
|
||||
encryption/decryption. use the bang (!) to force each (sub)key:
|
||||
create & verify signatures:
|
||||
$ date | gpg -u 'D9F57808!' --clearsign | gpg -v --verify
|
||||
$ date | gpg -u '3D7D41E3!' --clearsign | gpg -v --verify
|
||||
encrypt/decrypt:
|
||||
$ date | gpg -ear 'D9F57808!' | gpg -v --decrypt
|
||||
$ date | gpg -ear '1E88BF71!' | gpg -v --decrypt
|
||||
|
||||
* after testing out the keys locally, send your new public key to one or
|
||||
two people and test all key components (sending signed/encrypted messages
|
||||
to each other using all key components). make sure that they first delete
|
||||
(from their keyrings) your old key! and make sure that they understand that
|
||||
the key should NOT be circulated until all functions are verified to be
|
||||
working!
|
||||
|
||||
* when putting the new key into circulation, it's probably a good idea to
|
||||
expire/revoke the old key. include a revocation comment that specifies the
|
||||
new key ID and instructions to delete the old key from the keyring.
|
||||
|
||||
* note on key revocation: according to the OpenPGP standards a revocation
|
||||
generated by a sub key will be ignored, unless that subkey has been
|
||||
designated (by the primary key) as a revocation key. GnuPG seems to behave
|
||||
correctly, but some versions of PGP(tm) may not. if someone is claiming
|
||||
that your new key is revoked, have then remove all of your old and current
|
||||
keys from their keyring: then re-import your current key(s).
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GnuPG v1.2.4 (FreeBSD)
|
||||
|
||||
iD8DBQFApwlpnCgLvz19QeMRAhrpAJ4rhLrmVDjABh8CpPdTZ5jNMi7LsgCgp35S
|
||||
6qcUe4csx1p5AE2rAsvDi9c=
|
||||
=y7bA
|
||||
-----END PGP SIGNATURE-----
|
|
@ -90,7 +90,6 @@ class GPGBase(object):
|
|||
"""Base class for property storage and to control process initialisation."""
|
||||
|
||||
__metaclass__ = GPGMeta
|
||||
|
||||
_decode_errors = 'strict'
|
||||
_result_map = { 'crypt': _parsers.Crypt,
|
||||
'delete': _parsers.DeleteResult,
|
||||
|
@ -140,6 +139,12 @@ class GPGBase(object):
|
|||
log.error("GPGBase.__init__(): %s" % ae.message)
|
||||
raise RuntimeError(ae.message)
|
||||
else:
|
||||
if verbose is True:
|
||||
# The caller wants logging, but we need a valid --debug-level
|
||||
# for gpg. Default to "basic", and warn about the ambiguity.
|
||||
# (garrettr)
|
||||
verbose = "basic"
|
||||
log.warning('GPG(verbose=True) is ambiguous, defaulting to "basic" logging')
|
||||
self.verbose = verbose
|
||||
self.use_agent = use_agent
|
||||
|
||||
|
@ -498,19 +503,24 @@ class GPGBase(object):
|
|||
break
|
||||
lines.append(line)
|
||||
line = line.rstrip()
|
||||
if line[0:9] == '[GNUPG:] ':
|
||||
# Chop off the prefix
|
||||
line = line[9:]
|
||||
log.status("%s" % line)
|
||||
L = line.split(None, 1)
|
||||
keyword = L[0]
|
||||
if len(L) > 1:
|
||||
value = L[1]
|
||||
else:
|
||||
value = ""
|
||||
|
||||
if line.startswith('[GNUPG:]'):
|
||||
line = _util._deprefix(line, '[GNUPG:] ', log.status)
|
||||
keyword, value = _util._separate_keyword(line)
|
||||
result._handle_status(keyword, value)
|
||||
elif line[0:5] == 'gpg: ':
|
||||
log.warn("%s" % line)
|
||||
elif line.startswith('gpg:'):
|
||||
line = _util._deprefix(line, 'gpg: ')
|
||||
keyword, value = _util._separate_keyword(line)
|
||||
|
||||
# Log gpg's userland messages at our own levels:
|
||||
if keyword.upper().startswith("WARNING"):
|
||||
log.warn("%s" % value)
|
||||
elif keyword.upper().startswith("FATAL"):
|
||||
log.critical("%s" % value)
|
||||
# Handle the gpg2 error where a missing trustdb.gpg is,
|
||||
# for some stupid reason, considered fatal:
|
||||
if value.find("trustdb.gpg") and value.find("No such file"):
|
||||
result._handle_status('NEED_TRUSTDB', '')
|
||||
else:
|
||||
if self.verbose:
|
||||
log.info("%s" % line)
|
||||
|
@ -519,19 +529,29 @@ class GPGBase(object):
|
|||
result.stderr = ''.join(lines)
|
||||
|
||||
def _read_data(self, stream, result):
|
||||
"""Read the contents of the file from GPG's stdout."""
|
||||
"""Incrementally read from ``stream`` and store read data.
|
||||
|
||||
All data gathered from calling ``stream.read()`` will be concatenated
|
||||
and stored as ``result.data``.
|
||||
|
||||
:param stream: An open file-like object to read() from.
|
||||
:param result: An instance of one of the result parsing classes from
|
||||
:attr:`GPGBase._result_mapping`.
|
||||
"""
|
||||
chunks = []
|
||||
log.debug("Reading data from stream %r..." % stream.__repr__())
|
||||
|
||||
while True:
|
||||
data = stream.read(1024)
|
||||
if len(data) == 0:
|
||||
break
|
||||
log.debug("read from stdout: %r" % data[:256])
|
||||
chunks.append(data)
|
||||
if _util._py3k:
|
||||
log.debug("Read %4d bytes" % len(data))
|
||||
|
||||
# Join using b'' or '', as appropriate
|
||||
result.data = type(data)().join(chunks)
|
||||
else:
|
||||
result.data = ''.join(chunks)
|
||||
log.debug("Finishing reading from stream %r..." % stream.__repr__())
|
||||
log.debug("Read %4d bytes total" % len(result.data))
|
||||
|
||||
def _collect_output(self, process, result, writer=None, stdin=None):
|
||||
"""Drain the subprocesses output streams, writing the collected output
|
||||
|
@ -600,7 +620,8 @@ class GPGBase(object):
|
|||
return result
|
||||
|
||||
def _sign_file(self, file, default_key=None, passphrase=None,
|
||||
clearsign=True, detach=False, binary=False):
|
||||
clearsign=True, detach=False, binary=False,
|
||||
digest_algo='SHA512'):
|
||||
"""Create a signature for a file.
|
||||
|
||||
:param file: The file stream (i.e. it's already been open()'d) to sign.
|
||||
|
@ -609,6 +630,10 @@ class GPGBase(object):
|
|||
:param bool clearsign: If True, create a cleartext signature.
|
||||
:param bool detach: If True, create a detached signature.
|
||||
:param bool binary: If True, do not ascii armour the output.
|
||||
:param str digest_algo: The hash digest to use. Again, to see which
|
||||
hashes your GnuPG is capable of using, do:
|
||||
``$ gpg --with-colons --list-config digestname``.
|
||||
The default, if unspecified, is ``'SHA512'``.
|
||||
"""
|
||||
log.debug("_sign_file():")
|
||||
if binary:
|
||||
|
@ -629,6 +654,8 @@ class GPGBase(object):
|
|||
if default_key:
|
||||
args.append(str("--default-key %s" % default_key))
|
||||
|
||||
args.append(str("--digest-algo %s" % digest_algo))
|
||||
|
||||
## We could use _handle_io here except for the fact that if the
|
||||
## passphrase is bad, gpg bails and you can't write the message.
|
||||
result = self._result_map['sign'](self)
|
||||
|
@ -797,5 +824,5 @@ class GPGBase(object):
|
|||
% (data, type(data)))
|
||||
self._handle_io(args, data, result,
|
||||
passphrase=passphrase, binary=True)
|
||||
log.debug('GPG.encrypt_file(): Result: %r', result.data)
|
||||
log.debug("\n%s" % result.data)
|
||||
return result
|
||||
|
|
|
@ -525,11 +525,13 @@ def _get_options_group(group=None):
|
|||
'--default-recipient-self',
|
||||
'--detach-sign',
|
||||
'--export',
|
||||
'--export-ownertrust',
|
||||
'--export-secret-keys',
|
||||
'--export-secret-subkeys',
|
||||
'--fingerprint',
|
||||
'--fixed-list-mode',
|
||||
'--gen-key',
|
||||
'--import-ownertrust',
|
||||
'--list-config',
|
||||
'--list-key',
|
||||
'--list-keys',
|
||||
|
@ -747,6 +749,11 @@ def _get_all_gnupg_options():
|
|||
--merge-only --with-key-data
|
||||
--min-cert-level --yes
|
||||
""").split()
|
||||
|
||||
# These are extra options which only exist for GnuPG>=2.0.0
|
||||
three_hundred_eighteen.append('--export-ownertrust')
|
||||
three_hundred_eighteen.append('--import-ownertrust')
|
||||
|
||||
gnupg_options = frozenset(three_hundred_eighteen)
|
||||
return gnupg_options
|
||||
|
||||
|
@ -1283,6 +1290,8 @@ class Crypt(Verify):
|
|||
"MISSING_PASSPHRASE", "DECRYPTION_FAILED",
|
||||
"KEY_NOT_CREATED"):
|
||||
self.status = key.replace("_", " ").lower()
|
||||
elif key == "NEED_TRUSTDB":
|
||||
self._gpg._create_trustdb()
|
||||
elif key == "NEED_PASSPHRASE_SYM":
|
||||
self.status = 'need symmetric passphrase'
|
||||
elif key == "BEGIN_DECRYPTION":
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This file is part of python-gnupg, a Python interface to GnuPG.
|
||||
# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
|
||||
# © 2013 Andrej B.
|
||||
# © 2013 LEAP Encryption Access Project
|
||||
# © 2008-2012 Vinay Sajip
|
||||
# © 2005 Steve Traugott
|
||||
# © 2004 A.M. Kuchling
|
||||
#
|
||||
# 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 included LICENSE file for details.
|
||||
|
||||
'''trust.py
|
||||
-----------
|
||||
Functions for handling trustdb and trust calculations.
|
||||
|
||||
The functions within this module take an instance of :class:`gnupg.GPGBase` or
|
||||
a suitable subclass as their first argument.
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
|
||||
from . import _util
|
||||
from ._util import log
|
||||
|
||||
def _create_trustdb(cls):
|
||||
"""Create the trustdb file in our homedir, if it doesn't exist."""
|
||||
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
||||
if not os.path.isfile(trustdb):
|
||||
log.info("GnuPG complained that your trustdb file was missing. %s"
|
||||
% "This is likely due to changing to a new homedir.")
|
||||
log.info("Creating trustdb.gpg file in your GnuPG homedir.")
|
||||
cls.fix_trustdb(trustdb)
|
||||
|
||||
def export_ownertrust(cls, trustdb=None):
|
||||
"""Export ownertrust to a trustdb file.
|
||||
|
||||
If there is already a file named 'trustdb.gpg' in the current GnuPG
|
||||
homedir, it will be renamed to 'trustdb.gpg.bak'.
|
||||
|
||||
:param string trustdb: The path to the trustdb.gpg file. If not given,
|
||||
defaults to 'trustdb.gpg' in the current GnuPG homedir.
|
||||
"""
|
||||
if trustdb is None:
|
||||
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
||||
|
||||
try:
|
||||
os.rename(trustdb, trustdb + '.bak')
|
||||
except (OSError, IOError) as err:
|
||||
log.debug(err.message)
|
||||
|
||||
export_proc = cls._open_subprocess('--export-ownertrust')
|
||||
tdb = open(trustdb, 'wb')
|
||||
_util._threaded_copy_data(export_proc.stdout, tdb)
|
||||
|
||||
def import_ownertrust(self, trustdb=None):
|
||||
"""Import ownertrust from a trustdb file.
|
||||
|
||||
:param string trustdb: The path to the trustdb.gpg file. If not given,
|
||||
defaults to 'trustdb.gpg' in the current GnuPG homedir.
|
||||
"""
|
||||
if trustdb is None:
|
||||
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
||||
|
||||
import_proc = cls._open_subprocess('--import-ownertrust')
|
||||
tdb = open(trustdb, 'rb')
|
||||
_util._threaded_copy_data(tdb, import_proc.stdin)
|
||||
|
||||
def fix_trustdb(cls, trustdb=None):
|
||||
"""Attempt to repair a broken trustdb.gpg file.
|
||||
|
||||
GnuPG>=2.0.x has this magical-seeming flag: '--fix-trustdb'. You'd think
|
||||
it would fix the the trustdb. Hah! It doesn't. Here's what it does
|
||||
instead:
|
||||
|
||||
(python-gnupg)∃!isisⒶwintermute:(testing/digest-algo *$=)~/code/python-gnupg ∴ gpg2 --fix-trustdb
|
||||
gpg: You may try to re-create the trustdb using the commands:
|
||||
gpg: cd ~/.gnupg
|
||||
gpg: gpg2 --export-ownertrust > otrust.tmp
|
||||
gpg: rm trustdb.gpg
|
||||
gpg: gpg2 --import-ownertrust < otrust.tmp
|
||||
gpg: If that does not work, please consult the manual
|
||||
|
||||
Brilliant piece of software engineering right there.
|
||||
|
||||
:param string trustdb: The path to the trustdb.gpg file. If not given,
|
||||
defaults to 'trustdb.gpg' in the current GnuPG homedir.
|
||||
"""
|
||||
if trustdb is None:
|
||||
trustdb = os.path.join(cls.homedir, 'trustdb.gpg')
|
||||
export_proc = cls._open_subprocess('--export-ownertrust')
|
||||
import_proc = cls._open_subprocess('--import-ownertrust')
|
||||
_util._threaded_copy_data(export_proc.stdout, import_proc.stdin)
|
|
@ -219,6 +219,34 @@ def create_uid_email(username=None, hostname=None):
|
|||
|
||||
return uid
|
||||
|
||||
def _deprefix(line, prefix, callback=None):
|
||||
"""Remove the prefix string from the beginning of line, if it exists.
|
||||
|
||||
:param string line: A line, such as one output by GnuPG's status-fd.
|
||||
:param string prefix: A substring to remove from the beginning of
|
||||
``line``. Case insensitive.
|
||||
:type callback: callable
|
||||
:param callback: Function to call if the prefix is found. The signature to
|
||||
callback will be only one argument, the ``line`` without the ``prefix``, i.e.
|
||||
``callback(line)``.
|
||||
:rtype: string
|
||||
:returns: If the prefix was found, the ``line`` without the prefix is
|
||||
returned. Otherwise, the original ``line`` is returned.
|
||||
"""
|
||||
try:
|
||||
assert line.upper().startswith(u''.join(prefix).upper())
|
||||
except AssertionError:
|
||||
log.debug("Line doesn't start with prefix '%s':\n%s" % (prefix, line))
|
||||
return line
|
||||
else:
|
||||
newline = line[len(prefix):]
|
||||
if callback is not None:
|
||||
try:
|
||||
callback(newline)
|
||||
except Exception as exc:
|
||||
log.exception(exc)
|
||||
return newline
|
||||
|
||||
def _find_binary(binary=None):
|
||||
"""Find the absolute path to the GnuPG binary.
|
||||
|
||||
|
@ -231,23 +259,35 @@ def _find_binary(binary=None):
|
|||
:returns: The absolute path to the GnuPG binary to use, if no exceptions
|
||||
occur.
|
||||
"""
|
||||
gpg_binary = None
|
||||
found = None
|
||||
if binary is not None:
|
||||
if not os.path.isabs(binary):
|
||||
try: binary = _which(binary)[0]
|
||||
except IndexError as ie:
|
||||
log.error(ie.message)
|
||||
if binary is None:
|
||||
try: binary = _which('gpg')[0]
|
||||
except IndexError: raise RuntimeError("GnuPG is not installed!")
|
||||
try:
|
||||
assert os.path.isabs(binary), "Path to gpg binary not absolute"
|
||||
assert not os.path.islink(binary), "Path to gpg binary is symlink"
|
||||
assert os.access(binary, os.X_OK), "Lacking +x perms for gpg binary"
|
||||
found = _which(binary)
|
||||
log.debug("Found potential binary paths: %s"
|
||||
% '\n'.join([path for path in found]))
|
||||
found = found[0]
|
||||
except IndexError as ie:
|
||||
log.info("Could not determine absolute path of binary: '%s'"
|
||||
% binary)
|
||||
if found is None:
|
||||
try: found = _which('gpg')[0]
|
||||
except IndexError as ie:
|
||||
log.error("Could not find binary for 'gpg'.")
|
||||
try: found = _which('gpg2')[0]
|
||||
except IndexError as ie:
|
||||
log.error("Could not find binary for 'gpg2'.")
|
||||
if found is None:
|
||||
raise RuntimeError("GnuPG is not installed!")
|
||||
|
||||
try:
|
||||
assert os.path.isabs(found), "Path to gpg binary not absolute"
|
||||
assert not os.path.islink(found), "Path to gpg binary is symlink"
|
||||
assert os.access(found, os.X_OK), "Lacking +x perms for gpg binary"
|
||||
except (AssertionError, AttributeError) as ae:
|
||||
log.error(ae.message)
|
||||
else:
|
||||
return binary
|
||||
return found
|
||||
|
||||
def _has_readwrite(path):
|
||||
"""
|
||||
|
@ -406,6 +446,15 @@ def _now():
|
|||
"""Get a timestamp for right now, formatted according to ISO 8601."""
|
||||
return datetime.isoformat(datetime.now())
|
||||
|
||||
def _separate_keyword(line):
|
||||
"""Split the line, and return (first_word, the_rest)."""
|
||||
try:
|
||||
first, rest = line.split(None, 1)
|
||||
except ValueError:
|
||||
first = line.strip()
|
||||
rest = ''
|
||||
return first, rest
|
||||
|
||||
def _threaded_copy_data(instream, outstream):
|
||||
"""Copy data from one stream to another in a separate thread.
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ from __future__ import absolute_import
|
|||
from codecs import open as open
|
||||
|
||||
import encodings
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
|
@ -43,6 +44,7 @@ except ImportError:
|
|||
## see PEP-328 http://docs.python.org/2.5/whatsnew/pep-328.html
|
||||
from . import _parsers
|
||||
from . import _util
|
||||
from . import _trust
|
||||
from ._meta import GPGBase
|
||||
from ._parsers import _fix_unsafe
|
||||
from ._util import _is_list_or_tuple
|
||||
|
@ -76,6 +78,13 @@ class GPG(GPGBase):
|
|||
and private keyrings. Default is whatever GnuPG
|
||||
defaults to.
|
||||
|
||||
:param str,int,bool verbose: String or numeric value to pass to gpg's
|
||||
``--debug-level`` option. See the gpg man
|
||||
page for the list of valid options. If
|
||||
False, debug output is not generated by
|
||||
the gpg binary. If True, defaults to
|
||||
``--debug-level basic.``
|
||||
|
||||
:param str keyring: Name of keyring file containing public key data, if
|
||||
unspecified, defaults to 'pubring.gpg' in the
|
||||
``homedir`` directory.
|
||||
|
@ -152,6 +161,52 @@ class GPG(GPGBase):
|
|||
self.binary_version = version_line.split('\n')[0]
|
||||
log.debug("Using GnuPG version %s" % self.binary_version)
|
||||
|
||||
if _util._is_gpg2:
|
||||
# Make GnuPG>=2.0.0-only methods public:
|
||||
self.fix_trustdb = self._fix_trustdb
|
||||
self.import_ownertrust = self._import_ownertrust
|
||||
self.export_ownertrust = self._export_ownertrust
|
||||
|
||||
# Make sure that the trustdb exists, or else GnuPG will exit with
|
||||
# a fatal error (at least it does with GnuPG>=2.0.0):
|
||||
self._create_trustdb()
|
||||
|
||||
@functools.wraps(_trust._create_trustdb)
|
||||
def _create_trustdb(self):
|
||||
if self.is_gpg2():
|
||||
_trust._create_trustdb(self)
|
||||
else:
|
||||
log.info("Creating the trustdb is only available with GnuPG>=2.x")
|
||||
|
||||
@functools.wraps(_trust.fix_trustdb)
|
||||
def _fix_trustdb(self, trustdb=None):
|
||||
if self.is_gpg2():
|
||||
_trust.fix_trustdb(self)
|
||||
else:
|
||||
log.info("Fixing the trustdb is only available with GnuPG>=2.x")
|
||||
|
||||
@functools.wraps(_trust.import_ownertrust)
|
||||
def _import_ownertrust(self, trustdb=None):
|
||||
if self.is_gpg2():
|
||||
_trust.import_ownertrust(self)
|
||||
else:
|
||||
log.info("Importing ownertrust is only available with GnuPG>=2.x")
|
||||
|
||||
@functools.wraps(_trust.export_ownertrust)
|
||||
def _export_ownertrust(self, trustdb=None):
|
||||
if self.is_gpg2():
|
||||
_trust.export_ownertrust(self)
|
||||
else:
|
||||
log.info("Exporting ownertrust is only available with GnuPG>=2.x")
|
||||
|
||||
def is_gpg1(self):
|
||||
"""Returns true if using GnuPG <= 1.x."""
|
||||
return _util._is_gpg1(self.binary_version)
|
||||
|
||||
def is_gpg2(self):
|
||||
"""Returns true if using GnuPG >= 2.x."""
|
||||
return _util._is_gpg2(self.binary_version)
|
||||
|
||||
def sign(self, data, **kwargs):
|
||||
"""Create a signature for a message string or file.
|
||||
|
||||
|
@ -179,6 +234,10 @@ class GPG(GPGBase):
|
|||
:param bool clearsign: If True, create a cleartext signature.
|
||||
:param bool detach: If True, create a detached signature.
|
||||
:param bool binary: If True, do not ascii armour the output.
|
||||
:param str digest_algo: The hash digest to use. Again, to see which
|
||||
hashes your GnuPG is capable of using, do:
|
||||
``$ gpg --with-colons --list-config digestname``.
|
||||
The default, if unspecified, is ``'SHA512'``.
|
||||
"""
|
||||
if 'default_key' in kwargs.items():
|
||||
log.info("Signing message '%r' with keyid: %s"
|
||||
|
|
|
@ -152,24 +152,9 @@ def compare_keys(k1, k2):
|
|||
return k1 != k2
|
||||
|
||||
|
||||
class ResultStringIO(io.StringIO):
|
||||
def __init__(self, init_string):
|
||||
super(ResultStringIO, self).__init__(init_string)
|
||||
def write(self, data):
|
||||
super(ResultStringIO, self).write(unicode(data))
|
||||
|
||||
|
||||
class GPGTestCase(unittest.TestCase):
|
||||
""":class:`unittest.TestCase <TestCase>`s for python-gnupg."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Setup ``GPGTestCase`` and runtime environment for tests.
|
||||
|
||||
This function must be called manually.
|
||||
"""
|
||||
pass
|
||||
|
||||
def setUp(self):
|
||||
"""This method is called once per self.test_* method."""
|
||||
print("%s%s%s" % (os.linesep, str("=" * 70), os.linesep))
|
||||
|
@ -306,8 +291,9 @@ class GPGTestCase(unittest.TestCase):
|
|||
def test_make_args_drop_protected_options(self):
|
||||
"""Test that unsupported gpg options are dropped."""
|
||||
self.gpg.options = ['--tyrannosaurus-rex', '--stegosaurus']
|
||||
gpg_binary_path = _util._find_binary('gpg')
|
||||
cmd = self.gpg._make_args(None, False)
|
||||
expected = ['/usr/bin/gpg',
|
||||
expected = [gpg_binary_path,
|
||||
'--no-options --no-emit-version --no-tty --status-fd 2',
|
||||
'--homedir "%s"' % self.homedir,
|
||||
'--no-default-keyring --keyring %s' % self.keyring,
|
||||
|
@ -340,17 +326,30 @@ class GPGTestCase(unittest.TestCase):
|
|||
|
||||
def test_copy_data_bytesio(self):
|
||||
"""Test that _copy_data() is able to duplicate byte streams."""
|
||||
message = "This is a BytesIO string string in memory."
|
||||
message = "This is a BytesIO string."
|
||||
instream = io.BytesIO(message)
|
||||
self.assertEqual(unicode(message), instream.getvalue())
|
||||
outstream = ResultStringIO(u'result:')
|
||||
copied = outstream
|
||||
|
||||
out_filename = 'test-copy-data-bytesio'
|
||||
|
||||
# Create the test file:
|
||||
outfile = os.path.join(os.getcwdu(), out_filename)
|
||||
outstream = open(outfile, 'w+')
|
||||
|
||||
# _copy_data() will close both file descriptors
|
||||
_util._copy_data(instream, outstream)
|
||||
self.assertTrue(outstream.readable())
|
||||
|
||||
self.assertTrue(outstream.closed)
|
||||
self.assertFalse(instream.closed)
|
||||
self.assertTrue(copied.closed)
|
||||
#self.assertEqual(instream.getvalue()[6:], outstream.getvalue())
|
||||
self.assertTrue(os.path.isfile(outfile))
|
||||
|
||||
with open(outfile) as out:
|
||||
out.flush()
|
||||
out.seek(0)
|
||||
output = out.read()
|
||||
self.assertEqual(message, output)
|
||||
|
||||
os.remove(outfile)
|
||||
|
||||
def generate_key_input(self, real_name, email_domain, key_length=None,
|
||||
key_type=None, subkey_type=None, passphrase=None):
|
||||
|
@ -766,10 +765,8 @@ authentication."""
|
|||
|
||||
def test_encryption_multi_recipient(self):
|
||||
"""Test encrypting a message for multiple recipients"""
|
||||
self.gpg.homedir = _util._here
|
||||
|
||||
ian = { 'name_real': 'Ian Goldberg',
|
||||
'name_email': 'gold@stein',
|
||||
riggio = { 'name_real': 'Riggio',
|
||||
'name_email': 'ri@gg.io',
|
||||
'key_type': 'RSA',
|
||||
'key_length': 2048,
|
||||
'key_usage': '',
|
||||
|
@ -783,8 +780,8 @@ authentication."""
|
|||
##
|
||||
## gpg: keysize invalid; using 1024 bits
|
||||
##
|
||||
kat = { 'name_real': 'Kat Hannah',
|
||||
'name_email': 'kat@pics',
|
||||
sicari = { 'name_real': 'Sicari',
|
||||
'name_email': 'si@ca.ri',
|
||||
'key_type': 'RSA',
|
||||
'key_length': 2048,
|
||||
'key_usage': '',
|
||||
|
@ -793,18 +790,19 @@ authentication."""
|
|||
'subkey_usage': 'encrypt,sign',
|
||||
'passphrase': 'overalls' }
|
||||
|
||||
ian_input = self.gpg.gen_key_input(separate_keyring=True, **ian)
|
||||
riggio_input = self.gpg.gen_key_input(separate_keyring=True, **riggio)
|
||||
log.info("Key stored in separate keyring: %s" % self.gpg.temp_keyring)
|
||||
ian_key = self.gpg.gen_key(ian_input)
|
||||
ian_fpr = str(ian_key.fingerprint)
|
||||
self.gpg.options = ['--keyring {}'.format(ian_key.keyring)]
|
||||
riggio = self.gpg.gen_key(riggio_input)
|
||||
self.gpg.options = ['--keyring {}'.format(riggio.keyring)]
|
||||
riggio_key = self.gpg.export_keys(riggio.fingerprint)
|
||||
self.gpg.import_keys(riggio_key)
|
||||
|
||||
kat_input = self.gpg.gen_key_input(separate_keyring=True, **kat)
|
||||
sicari_input = self.gpg.gen_key_input(separate_keyring=True, **sicari)
|
||||
log.info("Key stored in separate keyring: %s" % self.gpg.temp_keyring)
|
||||
kat_key = self.gpg.gen_key(kat_input)
|
||||
kat_fpr = str(kat_key.fingerprint)
|
||||
self.gpg.options.append('--keyring {}'.format(kat_key.keyring))
|
||||
self.gpg.import_keys(kat_key.data)
|
||||
sicari = self.gpg.gen_key(sicari_input)
|
||||
self.gpg.options.append('--keyring {}'.format(sicari.keyring))
|
||||
sicari_key = self.gpg.export_keys(sicari.fingerprint)
|
||||
self.gpg.import_keys(sicari_key)
|
||||
|
||||
message = """
|
||||
In 2010 Riggio and Sicari presented a practical application of homomorphic
|
||||
|
@ -814,10 +812,12 @@ analysis of different kinds of data (temperature, humidity, etc.) coming from
|
|||
a WSN while ensuring both end-to-end encryption and hop-by-hop
|
||||
authentication."""
|
||||
|
||||
log.debug("kat_fpr type: %s" % type(kat_fpr))
|
||||
log.debug("ian_fpr type: %s" % type(ian_fpr))
|
||||
if self.gpg.is_gpg2:
|
||||
self.gpg.fix_trustdb()
|
||||
|
||||
encrypted = str(self.gpg.encrypt(message, ian_fpr, kat_fpr))
|
||||
encrypted = str(self.gpg.encrypt(message,
|
||||
riggio.fingerprint,
|
||||
sicari.fingerprint))
|
||||
log.debug("Plaintext: %s" % message)
|
||||
log.debug("Ciphertext: %s" % encrypted)
|
||||
|
||||
|
|
16
setup.py
16
setup.py
|
@ -18,22 +18,10 @@
|
|||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
|
||||
#______________________________________________________________________________
|
||||
#
|
||||
# NOTE: setuptools is currently (as of 27 May 2013) being merged back into its
|
||||
# parent project, distribute. By using the included distribute_setup.py
|
||||
# script, we make sure that we have a recent version of setuptools/distribute,
|
||||
# which is the *only* Python packaging framework compatible at this point with
|
||||
# both Python>=2.4 and Python3.x.
|
||||
#
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
## Upgrade setuptools to a version which supports Python 2 and 3
|
||||
#os.system('python ./distribute_setup.py')
|
||||
## Upgrade pip to a version with proper SSL support
|
||||
#os.system('python ./get-pip.py')
|
||||
|
||||
import setuptools
|
||||
import versioneer
|
||||
versioneer.versionfile_source = 'gnupg/_version.py'
|
||||
|
@ -56,10 +44,6 @@ through file descriptors. Input arguments are strictly checked and sanitised, \
|
|||
and therefore this module should be safe to use in networked applications \
|
||||
requiring direct user input. It is intended for use with Python 2.6 or \
|
||||
greater.
|
||||
|
||||
Documentation can be found on readthedocs_.
|
||||
|
||||
.. _readthedocs: https://python-gnupg.readthedocs.org/en/latest/
|
||||
""",
|
||||
license="GPLv3+",
|
||||
|
||||
|
|
Loading…
Reference in New Issue