sshfp-1.2.2/0000755000175000017500000000000011631547701012202 5ustar julienjuliensshfp-1.2.2/dane.10000644000175000017500000001252211631547701013175 0ustar julienjulien'\" t .\" Title: dane .\" Author: [see the "AUTHORS" section] .\" Generator: DocBook XSL Stylesheets v1.76.1 .\" Date: April 12, 2011 .\" Manual: Internet / DNS .\" Source: Paul Wouters .\" Language: English .\" .TH "DANE" "1" "April 12, 2011" "Paul Wouters" "Internet / DNS" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .\" http://bugs.debian.org/507673 .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" dane \- Generate TLSA/HASTLS DNS records by scanning SSL/TLS sites .SH "SYNTAX" .PP dane [\fB\-v\fR] [\fB\-q\fR] [\fB\-h\fR] [\fB\-v\fR] [\fB\-\-draft\fR|\fB\-\-rfc\fR] [\fB\-\-sha256\fR] [\fB\-\-sha512\fR] [\fB\-\-full\fR] [\fB\-\-insecure\fR] [\fB\-\-pubkey\fR] [\fB\-\-txt\fR] [\fB\-\-eecert\fR] [\fB\-\-cacert\fR] [\fB\-4\fR] [\fB\-6\fR] [\fB\-\-axfr\fR] [\fB\-n\fR <\fInameserver\fR>] \fIhost1\fR [\fIhost2 \&.\&.\&.]\fR] [\fI@nameserver]\fR] .SH "DESCRIPTION" .PP dane generates TLSA/HASTLS records based on the IETF DANE working group proposal\&. These are currently in draft, so private RRTYPE assignments are used\&. Records are generated by connecting to the website using SSL and grabbing its (EE) certificate\&. If the nameserver of the domain allows zone tranfers (AXFR), an entire domain can be processed for all its A/AAAA records\&. .SH "OPTIONS" .PP \fB\-n / \-\-nameserver\fR <\fIhostname1\fR> .RS 4 Use specified nameserver for AXFR query .RE .PP \fB\-q / \-\-quiet\fR .RS 4 Supress all warnings \- useful when scanning lots of host where some do not run SSL .RE .PP \fB\-\-axfr\fR .RS 4 Use AXFR\&. Implies \-n nameserver (or @nameserver)\&. Hosts are treated as zones to AXFR\&. .RE .PP \fB\-\-tlsa\fR .RS 4 Output TLSA record from SSL server scan results (default) .RE .PP \fB\-\-eecert\fR .RS 4 Output TLSA record format EE certificates (type 1) (default) .RE .PP \fB\-\-pubkey\fR .RS 4 Output TLSA record for just the public key (type unassined) (not implemented yet) .RE .PP \fB\-\-txt\fR .RS 4 Output Kaminsky style TXT record for (not implemented yet) .RE .PP \fB\-\-cacert\fR .RS 4 Output TLSA record for the entire CA chain and EE\-cert (not yet implemented) .RE .PP \fB\-\-sha256\fR .RS 4 Output TLSA record reference type 1 (SHA256) records (default) .RE .PP \fB\-\-sha512\fR .RS 4 Output TLSA record reference type 2 (SHA512) records .RE .PP \fB\-\-full\fR .RS 4 Output TLSA record reference type 0 (full cert) records .RE .PP \fB\-\-draft\fR .RS 4 Output Unknown Resource Record format with private RRTYPE assignment\&. This is used while the standard is still in draft form, and for when your nameserver does not (yet) support the new RRTYPE names\&. This option is the default (if \-\-rfc is not specified) as long as dane is has not be released as RFC\&. .RE .PP \fB\-\-rfc\fR .RS 4 Specify records using the RRTYPE\*(Aqs TLSA (and HASTLA) .RE .PP \fB\-\-insecure\fR .RS 4 Continue scanning even if the A/AAAA records could not be validated using DNSSEC .RE .PP \fB\-4\fR .RS 4 Only use ipv4 networking \- do not attempt to connect to AAAA SSL sites .RE .PP \fB\-6\fR .RS 4 Only use ipv6 networking \- do not attempt to connect to A SSL sites .RE .PP \fB\-h / \-\-help\fR .RS 4 Output help information and exit\&. .RE .PP \fB\-v / \-\-version\fR .RS 4 Output version information and exit\&. .RE .SH "FILES" .PP ~/\&.ssh/known_hosts .SH "REQUIREMENTS" .PP dane requires python\-dns and python\-argparse(\m[blue]\fBhttp://www\&.pythondns\&.org\fR\m[]) .PP Fedora: yum install python\-dns python\-argparse .PP Debian: apt\-get install python\-dnspython python\-argparse .SH "BUGS" .PP I\*(Aqm sure there are .SH "EXAMPLES" .PP typical usage: .PP dane www\&.xelerance\&.com .PP dane \-\-rfc \-\-sha512 www\&.xelerance\&.com .PP dane \-\-insecure \-\-draft xelerance\&.com @ns0\&.xelerance\&.net .SH "SEE ALSO" .PP \fBsshfp\fR(1) \fBssh\fR(1) and RFC\-XXXX .PP \m[blue]\fBhttp://www\&.xelerance\&.com/software/sshfp/\fR\m[] .PP \m[blue]\fBhttp://lists\&.xelerance\&.com/mailman/listinfo/sshfp/\fR\m[] .SH "AUTHORS" .PP Paul Wouters .SH "COPYRIGHT" .PP Copyright 2011 Xelerance Corporation .PP 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 2 of the License, or (at your option) any later version\&. See <\m[blue]\fBhttp://www\&.fsf\&.org/copyleft/gpl\&.txt\fR\m[]>\&. .PP 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 (file COPYING in the distribution) for more details\&. sshfp-1.2.2/dane.1.xml0000644000175000017500000001704611631547701014002 0ustar julienjulien April 12, 2011 dane 1 April 12, 2011 Paul Wouters Internet / DNS dane Generate TLSA/HASTLS DNS records by scanning SSL/TLS sites SYNTAX dane [] [] [] [] [|] [] [] [] [] [] [] [] [] [] [] [] [ <nameserver>] host1 [host2 ...]] [@nameserver]] DESCRIPTION dane generates TLSA/HASTLS records based on the IETF DANE working group proposal. These are currently in draft, so private RRTYPE assignments are used. Records are generated by connecting to the website using SSL and grabbing its (EE) certificate. If the nameserver of the domain allows zone tranfers (AXFR), an entire domain can be processed for all its A/AAAA records. OPTIONS <hostname1> Use specified nameserver for AXFR query Supress all warnings - useful when scanning lots of host where some do not run SSL Use AXFR. Implies -n nameserver (or @nameserver). Hosts are treated as zones to AXFR. Output TLSA record from SSL server scan results (default) Output TLSA record format EE certificates (type 1) (default) Output TLSA record for just the public key (type unassined) (not implemented yet) Output Kaminsky style TXT record for (not implemented yet) Output TLSA record for the entire CA chain and EE-cert (not yet implemented) Output TLSA record reference type 1 (SHA256) records (default) Output TLSA record reference type 2 (SHA512) records Output TLSA record reference type 0 (full cert) records Output Unknown Resource Record format with private RRTYPE assignment. This is used while the standard is still in draft form, and for when your nameserver does not (yet) support the new RRTYPE names. This option is the default (if --rfc is not specified) as long as dane is has not be released as RFC. Specify records using the RRTYPE's TLSA (and HASTLA) Continue scanning even if the A/AAAA records could not be validated using DNSSEC Only use ipv4 networking - do not attempt to connect to AAAA SSL sites Only use ipv6 networking - do not attempt to connect to A SSL sites Output help information and exit. Output version information and exit. FILES ~/.ssh/known_hosts REQUIREMENTS dane requires python-dns and python-argparse(http://www.pythondns.org) Fedora: yum install python-dns python-argparse Debian: apt-get install python-dnspython python-argparse BUGS I'm sure there are EXAMPLES typical usage: dane www.xelerance.com dane --rfc --sha512 www.xelerance.com dane --insecure --draft xelerance.com @ns0.xelerance.net SEE ALSO sshfp1 ssh1 and RFC-XXXX http://www.xelerance.com/software/sshfp/ http://lists.xelerance.com/mailman/listinfo/sshfp/ AUTHORS Paul Wouters <paul@xelerance.com> COPYRIGHT Copyright 2011 Xelerance Corporation 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 2 of the License, or (at your option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. 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 (file COPYING in the distribution) for more details. sshfp-1.2.2/fedora/0000755000175000017500000000000011631547701013442 5ustar julienjuliensshfp-1.2.2/fedora/sshfp.spec0000644000175000017500000000450211631547701015442 0ustar julienjulien%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")} Summary: Generate RFC-4255 SSHFP DNS records from knownhosts files or ssh-keyscan Name: sshfp Version: 1.2.2 Release: 1%{?dist} License: GPL Url: http://www.xelerance.com/software/%{name}/ Source: ftp://ftp.xelerance.com/%{name}/%{name}-%{version}.tar.gz Group: Applications/Internet BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) #Only to regenerate the man page #Buildrequires: xmlto Requires: python-dns, openssh-clients >= 4, python-argparse, ldns-python >= 1.6.10 BuildArch: noarch %description sshfp generates DNS SSHFP records from SSH public keys. sshfp can take public keys from a knownhosts file or from scanning the host's sshd daemon. The ssh client can use these SSHFP records if you set "VerifyHostKeyDNS yes" in the file /etc/ssh/ssh_config or in your .ssh/config. See RFC-4255 %prep %setup -q %build make all %install rm -rf ${RPM_BUILD_ROOT} export DESTDIR=${RPM_BUILD_ROOT} make install mkdir -p ${RPM_BUILD_ROOT}%{python_sitelib}/ cp daneldnsx.pyc ${RPM_BUILD_ROOT}%{python_sitelib}/ %clean rm -rf ${RPM_BUILD_ROOT} %files %defattr(-,root,root) %doc BUGS CHANGES README COPYING %{_bindir}/* %doc %{_mandir}/man1/* %{python_sitelib}/daneldnsx.* %changelog * Tue Sep 06 2011 Paul Wouters - 1.2.2-1 - Redone dane command with ipv6 support - Updated to draft-ietf-dane-protocol-10 * Tue Apr 12 2011 Paul Wouters - 1.2.0-1 - Released 1.2.0. - Added the dane command * Wed Oct 13 2010 Paul Wouters - 1.1.6-1 - Upgraded to 1.1.6 * Thu Apr 19 2007 Paul Wouters - 1.1.3-1 - Upgraded to 1.1.3 * Wed Oct 4 2006 Paul Wouters - 1.1.1-1 - Upgraded to 1.1.1 * Tue Sep 26 2006 Paul Wouters - 1.1.0-1 - Mistakingly ran the sha1() call on the uuencoded keyblob, which generated wrong SSHFP records. * Mon Sep 25 2006 Paul Wouters - 1.0.6-2 - Don't change VerifyHostKeyDNS in /etc/ssh/ssh_config anymore * Tue Sep 19 2006 Paul Wouters - 1.0.6-1 - Initial release for Fedora Extras sshfp-1.2.2/CHANGES0000644000175000017500000000601511631547701013177 0ustar julienjulienv1.2.2 (Sep 6, 2011) - Fix -4 / -6 flags [Chris] - Throw errors on stderr to improve pipe usage [Ludwig Nusse] - openSUSE package information on import error [Ludwig Nusse] - Fix nameserver by IP address (ldns workaround [Chris] - Fix case where AAAA is found but no IPv6 is available [Paul] v1.2.1 (April 25th, 2011) - Fix copyright information to clearly make it is all GPLv2+ [Paul] v1.2.0 (April 13th, 2011) - dane : Added the dane command (See IETF DANE working group) [Paul] - sshfp: Fix quiet for external commands [James Brown] - sshfp: Merged in subprocess changes [James Brown] - sshfp: Use optparse, some code refactoring [James Brown] - sshfp: Added -U UDP option [James Brown] - sshfp: Rename of some functions [James Brown] - sshfp: doclifted man page, generate when needed using xmlto [Paul] v1.1.6 (October 13th, 2010) - Fix quiet for external commands [James Brown] - Merged in subprocess changes [James Brown] - Use optparse, some code refactoring [James Brown] - Added -U UDP option [James Brown] - Rename of some functions [James Brown] v1.1.5 - Fixes for https://bugs.launchpad.net/ubuntu/+source/sshfp/+bug/355886 - Fix for sha Deprecation warning [Martin Jackson] - Fix for use of os.popen3 [Martin Jackson] - Sync debian/ with Debian (thanks) - Various small pylint fixes [Simon Deziel] v1.1.4 - Fixes to man page [Maximiliano Curia] - Fixes to Makefile for Debian [Maximiliano Curia] v1.1.3 - Moved trailing-dot option from -t to -d - Added -t / --type - Added -T / --timeout - Fixed printing of bogus blanc line at the end. - Use Makefile in Fedora spec file v1.1.2 - Added -p port option - Patch by andrew@lewman.com - Added Debian package [jacob] 'fakeroot debian/rules binary' - Fixed getopt order [paul] - Added a mailinglist: http://lists.xelerance.com/mailman/listinfo/sshfp/ v1.1.1 - Ignore hashed hostname entries in known_hosts files [paul] - Fix "sshfp -k -a" [paul] v1.1.0 - All SSHFP records were created badly using a sha1() on the uuencoded keyblow instead of the raw uudecoded keyblob. - Fixed getrecord -> getRecord call when using -a (AXFR) - Added a newline when -o is used, to make dnssigner happy v1.0.6 - Added man page v1.0.5 - Fixed Debian package pointer to be python-dnspython - Fixed trailing dot for ORIGIN entry when AXFR is used - Changed -d/--dns and -f/--knownhosts to -s/--scan and -k/--knownhosts [hugh daniel] - Add timestamp in named.conf comment syntax to axfr unless -q/--quiet is used. [paul] - Exclude localhost from AXFR since it leaks information about the host that ran sshfp [paul] - Moved files into git repository. anonymous git access at http://git.xelerance.com/ - Fix "sshfp -k hostname" where hostname is not a domainname's A record. v1.0.4 - Fixed option parsing [paul] - Hardened dns failures [patrick naubert] v1.0.3 - Public release of sshfp - Added fedora spec file for packaging v1.0.1 - Renamed knownhosts2sshfp to sshfp since it no longer reflects the function [paul] - Added ssh-keyscan option on suggestion of Jake Appelbaum [paul] v1.0.0 - Internal initial release sshfp-1.2.2/sshfp.1.xml0000644000175000017500000001646011631547701014215 0ustar julienjulien April 12, 2011 sshfp 1 April 12, 2011 Paul Wouters Internet / DNS sshfp Generate SSHFP DNS records from knownhosts files or ssh-keyscan SYNTAX sshfp [ <knownhosts_file>] [] [] | [<host1> [host2 ...]] sshfp [ <port>] [] <> [>] <domain1> [domain2] | <host1> [host2 ...] > DESCRIPTION sshfp generates RFC4255 SSHFP DNS records based on the public keys stored in a known_hosts file, which implies the user has previously trusted this key, or public keys can be obtained by using ssh-keyscan (1). Using ssh-keyscan (1) implies a secure path to connect to the hosts being scanned. It also implies a trust in the DNS to obtain the IP address of the hostname to be scanned. If the nameserver of the domain allows zone tranfers (AXFR), an entire domain can be processed for all its A records. OPTIONS <hostname1> [hostname2 ...] Scan hosts or domain for public SSH keys using ssh-keyscan knownhosts_file> <hostname1> [hostname2 ...] Obtain public SSH keys from a known_hosts file. Defaults to using ~/.ssh/known_hosts Scan all hosts in the known_hosts file when used with -k. When used with -s, it will attempt an zone transfer (AXFR) to obtain all A records in the domain specified. Add a trailing dot to the hostname in the SSHFP records. It is not possible to determine whether a known_hosts or dns query is for a FQDN (eg www.xelerance.com) or not (eg www) or not (unless -d domainname -a is used, in which case a trailing dot is always appended). Non-FQDN get their domainname appended through /etc/resolv.conf These non-FQDN will happen when using a non-FQDN (eg sshfp -k www) or known_hosts entries obtained by running ssh www.sub where .domain.com is implied. When -d is used, all hostnames not ending with a dot, that at least contain two parts in their hostname (eg www.sub but not www get a trailing dot. Note that the output of sshfp can also just be manually editted for trailing dots. <filename> Write to filename instead of stdout <portnumber> Use portnumber for scanning. Note that portnumbers do NOT appear in SSHFP records. Output help information and exit. Output version information and exit. Output less miscellany to stderr FILES ~/.ssh/known_hosts REQUIREMENTS sshfp requires python-dns (http://www.pythondns.org) Fedora: yum install python-dns Debian: apt-get install python-dnspython BUGS if a domain contains non-working glue A records, then ssh-keyscan aborts instead of skipping the single broken entry. This program can look up hashed hostnames in a known_hosts file if a recent-enough ssh-keygen is present EXAMPLES typical usage: sshfp (implies -k -a) sshfp -a -d (implies -k) sshfp -k bofh.xelerance.com (from known_hosts) sshfp -s bofh.xelerance.com (from a scan to the host) sshfp -k ~paul/.ssh/known_hosts bofh.xelerance.com www.openswan.org -o /tmp/mysshfp.txt sshfp -a -d -d xelerance.com -n ns0.xelerance.net >> /var/named/primary/xelerance.com SEE ALSO ssh-keyscan1 ssh1 and RFC-4255 http://www.xelerance.com/software/sshfp/ http://lists.xelerance.com/mailman/listinfo/sshfp/ AUTHORS Paul Wouters <paul@xelerance.com>, Jacob Appelbaum <jacob@appelbaum.net>, James Brown <jbrown@yelp.com> COPYRIGHT Copyright 2006-2010 Xelerance Corporation 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 2 of the License, or (at your option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. 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 (file COPYING in the distribution) for more details. sshfp-1.2.2/BUGS0000644000175000017500000000220111631547701012660 0ustar julienjulienKNOWN BUGS: 20110413 - dane for ipv6 always gives (2001:4178:2:1269::12): (-9, 'Address family for hostname not supported') even on systems with proper ipv6 support. 20100914 - sshfp -k ~/.ssh/known_hosts -a does not work as expected, because of internal confusion about -k requiring an argument or not (-k vs -s) 20061101 - IPv6 untested and might not work at all. 20061016 - Scanning in-addr.arpa for PTR records to add sshfp records is missing. 20061004 - Using opts.append(x) does not work, as x is never appended to the current loop. This means the whole option processing needs to become re-entrant after fixing an argument. yuck. 20060927 - sshfp -a xelerance.com @ns1.xelerance.com does not work as expected. - Running sshfp against the same nameserver twice using -a @ns0 gives different output all the time. Looks like ssh-keyscan is sensitive to failures. 20060921: - If a zone contains non-working glue A records, then ssh-keyscan aborts. Bug reported upstream http://bugzilla.mindrot.org/show_bug.cgi?id=1213 FEATURE reqeust: - Some mode (-V) that verifies all keys in knownhosts file and compares them with a scan sshfp-1.2.2/sshfp.10000644000175000017500000001325311631547701013413 0ustar julienjulien'\" t .\" Title: sshfp .\" Author: [see the "AUTHORS" section] .\" Generator: DocBook XSL Stylesheets v1.76.1 .\" Date: April 12, 2011 .\" Manual: Internet / DNS .\" Source: Paul Wouters .\" Language: English .\" .TH "SSHFP" "1" "April 12, 2011" "Paul Wouters" "Internet / DNS" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .\" http://bugs.debian.org/507673 .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" sshfp \- Generate SSHFP DNS records from knownhosts files or ssh\-keyscan .SH "SYNTAX" .PP sshfp [\fB\-k\fR <\fIknownhosts_file\fR>] [\fB\-d\fR] [\fB\-a\fR] | [<\fIhost1\fR> [\fIhost2 \&.\&.\&.]\fR] sshfp \fB\-s\fR [\fB\-p\fR <\fIport\fR>] [\fB\-d\fR] <\fB\-a\fR> [\fB\-n \fI] [\fIdomain2\fR] | <\fIhost1\fR> [\fIhost2 \&.\&.\&.\fR] > .SH "DESCRIPTION" .PP sshfp generates RFC4255 SSHFP DNS records based on the public keys stored in a known_hosts file, which implies the user has previously trusted this key, or public keys can be obtained by using ssh\-keyscan (1)\&. Using ssh\-keyscan (1) implies a secure path to connect to the hosts being scanned\&. It also implies a trust in the DNS to obtain the IP address of the hostname to be scanned\&. If the nameserver of the domain allows zone tranfers (AXFR), an entire domain can be processed for all its A records\&. .SH "OPTIONS" .PP \fB\-s / \-\-scan\fR <\fIhostname1\fR> [hostname2 \&.\&.\&.] .RS 4 Scan hosts or domain for public SSH keys using ssh\-keyscan .RE .PP \fB\-k / \-\-knownhosts <\fR\fIknownhosts_file\fR\fI> <\fR\fIhostname1\fR\fI> [hostname2 \&.\&.\&.]\fR .RS 4 Obtain public SSH keys from a known_hosts file\&. Defaults to using ~/\&.ssh/known_hosts .RE .PP \fB\-a / \-\-all\fR .RS 4 Scan all hosts in the known_hosts file when used with \-k\&. When used with \-s, it will attempt an zone transfer (AXFR) to obtain all A records in the domain specified\&. .RE .PP \fB\-d / \-\-trailing\-dot\fR .RS 4 Add a trailing dot to the hostname in the SSHFP records\&. It is not possible to determine whether a known_hosts or dns query is for a FQDN (eg www\&.xelerance\&.com) or not (eg www) or not (unless \-d domainname \-a is used, in which case a trailing dot is always appended)\&. Non\-FQDN get their domainname appended through /etc/resolv\&.conf These non\-FQDN will happen when using a non\-FQDN (eg sshfp \-k www) or known_hosts entries obtained by running ssh www\&.sub where \&.domain\&.com is implied\&. When \-d is used, all hostnames not ending with a dot, that at least contain two parts in their hostname (eg www\&.sub but not www get a trailing dot\&. Note that the output of sshfp can also just be manually editted for trailing dots\&. .RE .PP \fB\-o / \-\-output\fR <\fIfilename\fR> .RS 4 Write to filename instead of stdout .RE .PP \fB\-p / \-\-port\fR <\fIportnumber\fR> .RS 4 Use portnumber for scanning\&. Note that portnumbers do NOT appear in SSHFP records\&. .RE .PP \fB\-h / \-\-help\fR .RS 4 Output help information and exit\&. .RE .PP \fB\-v / \-\-version\fR .RS 4 Output version information and exit\&. .RE .PP \fB\-q / \-\-quiet\fR .RS 4 Output less miscellany to stderr .RE .SH "FILES" .PP ~/\&.ssh/known_hosts .SH "REQUIREMENTS" .PP sshfp requires python\-dns (\m[blue]\fBhttp://www\&.pythondns\&.org\fR\m[]) .PP Fedora: yum install python\-dns .PP Debian: apt\-get install python\-dnspython .SH "BUGS" .PP if a domain contains non\-working glue A records, then ssh\-keyscan aborts instead of skipping the single broken entry\&. .PP This program can look up hashed hostnames in a known_hosts file if a recent\-enough ssh\-keygen is present .SH "EXAMPLES" .PP typical usage: .PP sshfp (implies \-k \-a) .PP sshfp \-a \-d (implies \-k) .PP sshfp \-k bofh\&.xelerance\&.com (from known_hosts) .PP sshfp \-s bofh\&.xelerance\&.com (from a scan to the host) .PP sshfp \-k ~paul/\&.ssh/known_hosts bofh\&.xelerance\&.com www\&.openswan\&.org \-o /tmp/mysshfp\&.txt .PP sshfp \-a \-d \-d xelerance\&.com \-n ns0\&.xelerance\&.net >> /var/named/primary/xelerance\&.com .SH "SEE ALSO" .PP \fBssh-keyscan\fR(1) \fBssh\fR(1) and RFC\-4255 .PP \m[blue]\fBhttp://www\&.xelerance\&.com/software/sshfp/\fR\m[] .PP \m[blue]\fBhttp://lists\&.xelerance\&.com/mailman/listinfo/sshfp/\fR\m[] .SH "AUTHORS" .PP Paul Wouters , Jacob Appelbaum , James Brown .SH "COPYRIGHT" .PP Copyright 2006\-2010 Xelerance Corporation .PP 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 2 of the License, or (at your option) any later version\&. See <\m[blue]\fBhttp://www\&.fsf\&.org/copyleft/gpl\&.txt\fR\m[]>\&. .PP 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 (file COPYING in the distribution) for more details\&. sshfp-1.2.2/libdane.py-unused0000644000175000017500000000771311631547701015463 0ustar julienjulienimport sys import binascii import ssl, socket import hashlib import ldnsx import warnings secure, transport, quiet, fmt = True, "both", False, "draft" def create_txt(hostname, pubkey): """ Kaminsky / Gilmore type TLS pubkey in TXT RRtype -- testing version""" warnings.warn("create_txt is untested...") hashobj = hashlib.sha1() #Are there other valid hashes? hashobj.update(pubkey) #Should we try and get the pubkey ourselves, like in create_TLSA? result = hashobj.hexdigest().upper() return "%s IN TXT \"v=key1 ha=sha1 h=%s\"" % (hostname, result) def create_hastls(hostname, fallback_default, services): """ Creates a HASTLS RRtype """ def checkExistingTLSA(hostname, certtype, reftype): """ check if a TLSA record already exists, to compare and notify if update is needed UNTESTED!!!! """ warnings.warn("checkExistingTLSA is untested...") res = ldnsx.resolver() #Get the appropriate resource records... pkt = res.query(hostname, "TYPE65468", tries = 3) pres_TLSAs=set(pkt.answer(rr_type="TYPE65468")) pres_TLSAs = map(lambda rr: str(rr).strip(), pres_TLSAs) TLSAs = set(create_TLSAs(hostname, certtype,reftype).split('\n')) return TLSAs == pres_TLSAs # are things the same as before? def checkExistingHASTLS(address): """ check if a HASTLS record already exists, to compare and notify if update is needed """ def create_tlsa(hostname, certtype, reftype): """Creates a TLSA RRtype""" """ get all A/AAAA records for hostname """ if secure: pkt = ldnsx.secure_query(hostname, "ANY", flex=quiet) else: pkt = ldnsx.query(hostname, "ANY") records = pkt.answer( rr_type = { "ipv4":"A", "ipv6":"AAAA", "both":"A|AAAA" }[transport] ) drafts, rfcs = [], [] for rr in records: draft = genTLSA(hostname, rr.ip(), certtype, reftype, draft=True) rfc = genTLSA(hostname, rr.ip(), certtype, reftype, draft=False) if draft and not (draft in drafts): drafts.append(draft.strip()) if rfc and not (rfc in rfcs): rfcs.append(rfc.strip()) ret = "" if not fmt == "rfc": ret += "\n".join(drafts) if not fmt == "draft": ret += "\n".join(rfcs) return ret def genTLSA(hostname, address, certtype, reftype, draft=True): try: # errors will be thrown already before we get here dercert = get_cert(hostname, address) except: return if not dercert: return if certtype != 1: raise Exception("Only EE-cert supported right now") certhex = hashCert(reftype, dercert) if draft: # octet length is half of the string length; remember certtype and reftype are part of the length so +2 return "_443._tcp.%s IN TYPE65468 \# %s 0%s0%s%s"%(hostname, len(certhex)/2+2, certtype, reftype, certhex ) else: return "_443._tcp.%s IN TLSA %s %s %s"%(hostname, certtype, reftype, certhex) def get_cert(hostname, address): # We don't use ssl.get_server_certificate because it does not support IPv6, and it converts DER to PEM, which # we would just have to convert back to DER using ssl.PEM_cert_to_DER_cert() try: # kinda ugly kludge if ":" in address: conn = socket.socket(socket.AF_INET6) else: conn = socket.socket(socket.AF_INET) conn.connect((address, 443)) except socket.error , e: #raise Exception("%s (%s): %s"%(hostname, address, str(e))) print >> sys.stderr, "%s (%s): %s"%(hostname, address, str(e)) return try: sock = ssl.wrap_socket(conn) except ssl.SSLError , e: #raise Exception("%s (%s): %s"%(hostname, address, str(e))) print >> sys.stderr, "%s (%s): %s"%(hostname, address, str(e)) return try: dercert = sock.getpeercert(True) except AttributeError , e: #print >> sys.stderr, "%s (%s): %s"%(hostname, address, str(e)) return sock.close() conn.close() return dercert # take PEM encoded EE-cert and DER encode it, then sha256 it def hashCert(reftype,certblob): if reftype == 0: return binascii.b2a_hex(certblob).upper() elif reftype == 1: hashobj = hashlib.sha256() hashobj.update(certblob) elif reftype == 2: hashobj = hashlib.sha512() hashobj.update(certblob) else: return 0 return hashobj.hexdigest().upper() sshfp-1.2.2/sshfp0000755000175000017500000002540511631547701013261 0ustar julienjulien#!/usr/bin/python # Generate SSHFP DNS records (RFC4255) from knownhosts files or ssh-keyscan # Copyright by Xelerance http://www.xelerance.com/ # Paul Wouters # License: GNU GENERAL PUBLIC LICENSE Version 2 or later import os import sys import subprocess import optparse import base64 import time # www.dnspython.org try: import dns.resolver import dns.query import dns.zone except ImportError: print >> sys.stderr, "sshfp requires the python-dns package from http://www.pythondns.org/" print >> sys.stderr, "Fedora: yum install python-dns" print >> sys.stderr, "Debian: apt-get install python-dnspython (NOT python-dns!)" print >> sys.stderr, "openSUSE: zypper in python-dnspython" sys.exit(1) try: import hashlib digest = hashlib.sha1 except ImportError: import sha digest = sha.new global all_hosts global khfile global hostnames global trailing global quiet global port global timeout global algo VERSION = "1.2.2" DEFAULT_KNOWN_HOSTS_FILE = "~/.ssh/known_hosts" def show_version(): print >> sys.stderr, "sshfp version: " + VERSION print >> sys.stderr, "Source : http://www.xelerance.com/software/sshfp/" print >> sys.stderr, "Author:\n Paul Wouters " print >> sys.stderr, " James Brown " def create_sshfp(hostname, keytype, keyblob): """Creates an SSH fingerprint""" if keytype == "ssh-rsa": keytype = "1" else: if keytype == "ssh-dss": keytype = "2" else: return "" try: rawkey = base64.b64decode(keyblob) except TypeError: print >> sys.stderr, "FAILED on hostname "+hostname+" with keyblob "+keyblob return "ERROR" fpsha1 = digest(rawkey).hexdigest().upper() # check for Reverse entries reverse = 1 parts = hostname.split(".", 3) if parts[0] != hostname: for octet in parts: if not octet.isdigit(): reverse = 0 if reverse: hostname = parts[3] + "." + parts[2] + "." + parts[1] + "." + parts[0] + ".in-addr.arpa." # we don't know wether we need a trailing dot :( # eg if someone did "ssh ns.foo" we don't know if this really is "ns.foo." or "ns.foo" plus resolv.conf domainname if trailing and not reverse: if hostname[-1:] != ".": hostname = hostname + "." return hostname + " IN SSHFP " + keytype + " 1 " + fpsha1 def get_known_host_entry(known_hosts, host): """Get a single entry out of a known_hosts file Uses the ssh-keygen utility.""" cmd = ["ssh-keygen", "-f", known_hosts, "-F", host] process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = process.communicate() if not quiet and stderr: print >> sys.stderr, stderr outputl = [] for e in stdout.split("\n"): if not e.startswith("#"): outputl.append(e) return "\n".join(outputl) def sshfp_from_file(khfile, wantedHosts): global all_hosts global algo # ok, let's do it known_hosts = os.path.expanduser(khfile) try: khfp = open(known_hosts) except IOError: print >> sys.stderr, "ERROR: failed to open file "+ known_hosts sys.exit(1) hashed_known_hosts = False if khfp.readline().startswith("|1|"): hashed_known_hosts = True khfp.close() fingerprints = [] if hashed_known_hosts and all_hosts: print >> sys.stderr, "ERROR: %s is hashed, cannot use with -a" % known_hosts sys.exit(1) elif hashed_known_hosts: #only looking for some known hosts for host in wantedHosts: fingerprints.append(process_record(get_known_host_entry(known_hosts, host), host)) else: try: khfp = open(known_hosts) except IOError: print >> sys.stderr, "ERROR: failed to open file "+ known_hosts sys.exit(1) data = khfp.read() khfp.close() fingerprints.append(process_records(data, wantedHosts)) return "\n".join(fingerprints) def check_keytype(keytype): global algos for algo in algos: if "ssh-%s" % algo[:-1] == keytype[:-1]: return True if not quiet: print >> sys.stderr, "Could only find key type %s for %s" % (keytype, hostname) return False def process_record(record, hostname): (host, keytype, key) = record.split(" ") key = key.rstrip() if check_keytype(keytype): record = create_sshfp(hostname, keytype, key) return record return "" def process_records(data, hostnames): """Process all records in a string. If the global "all_hosts" is True, then return SSHFP entries for all records with the allowed key types. If "all_hosts is False and hostnames is non-empty, return only the items in hostnames """ all_records = [] for record in data.split("\n"): if record.startswith("#") or not record: continue try: (host, keytype, key) = record.split(" ") except ValueError: if not quiet: print >> sys.stdout, "Print unable to read record '%s'" % record continue if "," in host: host = host.split(",")[0] if all_hosts or host in hostnames or host == hostnames: if not check_keytype(keytype): continue all_records.append(create_sshfp(host, keytype, key)) if all_records: all_records.sort() return "\n".join(all_records) else: return "" def get_record(domain, qtype): try: answers = dns.resolver.query(domain, qtype) except dns.resolver.NXDOMAIN: #print "NXdomain: "+domain return 0 except dns.resolver.NoAnswer: #print "NoAnswer: "+domain return 0 for rdata in answers: # just return first entry we got, answers[0].target does not work if qtype == "A": return rdata if qtype == "NS": return str(rdata.target) else: print >> sys.stderr, "ERROR: error in get_record, unknown type " + qtype sys.exit(1) def get_axfr_record(domain, nameserver): try: zone = dns.zone.from_xfr(dns.query.xfr(nameserver, domain, rdtype=dns.rdatatype.AXFR)) except dns.exception.FormError: raise dns.exception.FormError, domain else: return zone def sshfp_from_axfr(domain, nameserver): if " " in domain: print >> sys.stderr, "ERROR: space in domain '"+domain+"' can't be right, aborted" sys.exit(1) if not nameserver: nameserver = get_record(domain, "NS") if not nameserver: print >> sys.stderr, "WARNING: no NS record found for domain "+domain+". trying as host record instead" # better then nothing return sshfp_from_dns([domain]) hosts = [] #print "nameserver:" + str(ns) try: # print "trying axfr for "+domain+"@"+nameserver axfr = get_axfr_record(domain, nameserver) except dns.exception.FormError, badDomain: print >> sys.stderr, "AXFR error: %s - No permission or not authorative for %s; aborting" % (nameserver, badDomain) sys.exit(1) for (name, ttl, rdata) in axfr.iterate_rdatas('A'): #print "name:" +str(name) +", ttl:"+ str(ttl)+ ", rdata:"+str(rdata) if "@" in str(name): hosts.append(domain + ".") else: if not str(name) == "localhost": hosts.append("%s.%s." % (name, domain)) return sshfp_from_dns(hosts) def sshfp_from_dns(hosts): global quiet global port global timeout global algos cmd = ["ssh-keyscan", "-p", str(port), "-T", str(timeout), "-t", ",".join(algos)] + hosts process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = process.communicate() if not quiet: print >> sys.stderr, stderr khdns = stdout return process_records(khdns, hosts) def main(): global all_hosts global trailing global quiet global port global timeout global algos parser = optparse.OptionParser() parser.add_option("-k", "--knownhosts", "--known-hosts", action="store", dest="known_hosts", metavar="KNOWN_HOSTS_FILE", default=None, help="obtain public ssh keys from the known_hosts file KNOWN_HOSTS_FILE") parser.add_option("-s", "--scan", action="store_true", default=False, dest="scan", help="scan the listed hosts for public keys using ssh-keyscan") parser.add_option("-a", "--all", action="store_true", dest="all_hosts", help="scan all hosts in the known_hosts file when used with -k. When used with -s, attempt a zone transfer to obtain all A records in the domain provided") parser.add_option("-d", "--trailing-dot", action="store_true", dest="trailing_dot", help="add a trailing dot to the hostname in the SSHFP records") parser.add_option("-o", "--output", action="store", metavar="FILENAME", default=None, help="write to FILENAME instead of stdout") parser.add_option("-p", "--port", action="store", metavar="PORT", type="int", default=22, help="use PORT for scanning") parser.add_option("-v", "--version", action="store_true", dest="version", help="print version information and exit") parser.add_option("-q", "--quiet", action="store_true", dest="quiet") parser.add_option("-T", "--timeout", action="store", type="int", dest="timeout", default=5, help="scanning timeout (default %default)") parser.add_option("-t", "--type", action="append", type="choice", dest="algo", choices=["rsa", "dsa"], default=[], help="key type to fetch (may be specified more than once, default dsa,rsa)") parser.add_option("-n", "--nameserver", action="store", type="string", dest="nameserver", default="", help="nameserver to use for AXFR (only valid with -s -a)") (options, args) = parser.parse_args() # parse options khfile = options.known_hosts dodns = options.scan nameserver = "" domain = "" output = options.output quiet = options.quiet data = "" trailing = options.trailing_dot timeout = options.timeout algos = options.algo or ["dsa", "rsa"] all_hosts = options.all_hosts port = options.port hostnames = () if options.version: show_version() sys.exit(0) if not quiet and port != 22: print >> sys.stderr, "WARNING: non-standard port numbers are not designated in SSHFP records" if not quiet and options.known_hosts and options.scan: print >> sys.stderr, "WARNING: Ignoring -k option, -s was passwd" if options.nameserver and not options.scan and not options.all_hosts: print >> sys.stderr, "ERROR: Cannot specify -n without -s and -a" sys.exit(1) if not options.scan and options.all_hosts and args: print >> sys.stderr, "WARNING: -a and hosts both passed, ignoring manual host list" if not args: print >> sys.stderr, "WARNING: Assuming -a" all_hosts = True if not options.scan: khfile = DEFAULT_KNOWN_HOSTS_FILE if options.scan and options.all_hosts: datal = [] for arg in args: datal.append(sshfp_from_axfr(arg, options.nameserver)) if not quiet: datal.insert(0, ";") datal.insert(0, "; Generated by sshfp %s from %s at %s" % (VERSION, nameserver, time.ctime())) datal.insert(0, ";") data = "\n".join(datal) elif options.scan: # Scan specified hosts if not args: print >> sys.stderr, "ERROR: You asked me to scan, but didn't give any hosts to scan" sys.exit(1) data = sshfp_from_dns(args) else: # known_hosts data = sshfp_from_file(khfile, args) if output: try: fp = open(output, "w") except IOError: print >> sys.stderr, "ERROR: can't open '%s'' for writing" % output sys.exit(1) else: fp.write(data) fp.close() else: print data if __name__ == "__main__": main() sshfp-1.2.2/TODO0000644000175000017500000000013711631547701012673 0ustar julienjulien- support ixfr with serial to allow to receive only changes since.... dig supports this option sshfp-1.2.2/dane0000755000175000017500000002141311631547701013040 0ustar julienjulien#!/usr/bin/python # dane is a tool to generate and verify HASTLS and TLSA records # By Paul Wouters and Christopher Olah # Copyright 2011 by Xelerance http://www.xelerance.com/ # License: GNU GENERAL PUBLIC LICENSE Version 2 or later # # https://datatracker.ietf.org/wg/dane/charter/ # https://datatracker.ietf.org/doc/draft-ietf-dane-protocol/ import sys import binascii import ssl, socket import hashlib import warnings try: import ldnsx except: import daneldnsx as ldnsx try: import sys import argparse except ImportError , e: module = str(e)[16:] print >> sys.stderr, "dane requires the python module " + module if module in ["argparse", "ipcalc", "ldns"]: print >> sys.stderr, "Fedora/CentOS: yum install " \ + {"argparse":"python-argparse", "ipcalc": "python-ipcalc", "ldns": "ldns-python"} print >> sys.stderr, "Debian/Ubuntu: apt-get install " \ + {"argparse":"python-argparse", "ipcalc": "python-ipcalc", "ldns": "python-ldns"} print >> sys.stderr, "openSUSE: zypper in " \ + {"argparse":"python-argparse", "ipcalc": "python-ipcalc", "ldns": "python-ldns"} sys.exit(1) def create_txt(hostname, pubkey): """ Kaminsky / Gilmore type TLS pubkey in TXT RRtype -- testing version""" warnings.warn("create_txt is untested...") hashobj = hashlib.sha1() #Are there other valid hashes? hashobj.update(pubkey) #Should we try and get the pubkey ourselves, like in create_TLSA? result = hashobj.hexdigest().upper() return "%s IN TXT \"v=key1 ha=sha1 h=%s\"" % (hostname, result) def create_hastls(hostname, fallback_default, services): """ Creates a HASTLS RRtype """ def checkExistingTLSA(hostname, certtype, reftype): """ check if a TLSA record already exists, to compare and notify if update is needed UNTESTED!!!! """ warnings.warn("checkExistingTLSA is untested...") res = ldnsx.resolver() #Get the appropriate resource records... pkt = res.query(hostname, "TYPE65468", tries = 3) pres_TLSAs=set(pkt.answer(rr_type="TYPE65468")) pres_TLSAs = map(lambda rr: str(rr).strip(), pres_TLSAs) TLSAs = set(create_TLSAs(hostname, certtype,reftype).split('\n')) return TLSAs == pres_TLSAs # are things the same as before? def checkExistingHASTLS(address): """ check if a HASTLS record already exists, to compare and notify if update is needed """ def create_tlsa(hostname, certtype, reftype): """Creates a TLSA RRtype""" """ get all A/AAAA records for hostname """ if secure: pkt = ldnsx.secure_query(hostname, "ANY", flex=True) else: pkt = ldnsx.query(hostname, "ANY") records = pkt.answer( rr_type = { "ipv4":"A", "ipv6":"AAAA", "both":"A|AAAA" }[transport] ) drafts, rfcs = [], [] for rr in records: draft = genTLSA(hostname, rr.ip(), certtype, reftype, draft=True) rfc = genTLSA(hostname, rr.ip(), certtype, reftype, draft=False) if draft and not (draft in drafts): drafts.append(draft.strip()) if rfc and not (rfc in rfcs): rfcs.append(rfc.strip()) ret = "" if not fmt == "rfc": ret += "\n".join(drafts) if not fmt == "draft": ret += "\n".join(rfcs) return ret def genTLSA(hostname, address, certtype, reftype, draft=True): try: # errors will be thrown already before we get here dercert = get_cert(hostname, address) except: return if not dercert: return if certtype != 1: raise Exception("Only EE-cert supported right now") certhex = hashCert(reftype, dercert) if draft: # octet length is half of the string length; remember certtype and reftype are part of the length so +2 return "_443._tcp.%s IN TYPE65468 \# %s 0%s0%s%s"%(hostname, len(certhex)/2+2, certtype, reftype, certhex ) else: return "_443._tcp.%s IN TLSA %s %s %s"%(hostname, certtype, reftype, certhex) def get_cert(hostname, address): # We don't use ssl.get_server_certificate because it does not support IPv6, and it converts DER to PEM, which # we would just have to convert back to DER using ssl.PEM_cert_to_DER_cert() try: # kinda ugly kludge if ":" in address: conn = socket.socket(socket.AF_INET6) else: conn = socket.socket(socket.AF_INET) conn.connect((address, 443)) except socket.error , e: #raise Exception("%s (%s): %s"%(hostname, address, str(e))) print >> sys.stderr, "%s (%s): %s"%(hostname, address, str(e)) return try: sock = ssl.wrap_socket(conn) except ssl.SSLError , e: #raise Exception("%s (%s): %s"%(hostname, address, str(e))) print >> sys.stderr, "%s (%s): %s"%(hostname, address, str(e)) return try: dercert = sock.getpeercert(True) except AttributeError , e: #print >> sys.stderr, "%s (%s): %s"%(hostname, address, str(e)) return sock.close() conn.close() return dercert # take PEM encoded EE-cert and DER encode it, then sha256 it def hashCert(reftype,certblob): if reftype == 0: return binascii.b2a_hex(certblob).upper() elif reftype == 1: hashobj = hashlib.sha256() hashobj.update(certblob) elif reftype == 2: hashobj = hashlib.sha512() hashobj.update(certblob) else: return 0 return hashobj.hexdigest().upper() secure, transport, quiet, fmt = True, "both", False, "draft" # create the parser parser = argparse.ArgumentParser(description='Create TLS related DNS records for hosts or an entire zone. version 1.2.1') # AXFR parser.add_argument('-n', '--nameserver', metavar="nameserver", action='append', help='nameserver to query') parser.add_argument('--axfr', action='store_true', help='use AXFR (all A/AAAA records will be scanned)') # IETF status related, currently --draft is the default parser.add_argument('--draft', action='store_true',help='output in draft private rrtype (65468/65469) format (default)') parser.add_argument('--rfc', action='store_true',help='output in rfc (TLSA/HASTLS) rrtype format') # TLSA related parser.add_argument('--tlsa', action='store_true',help='generate TLSA record (default:yes)') parser.add_argument('--eecert', action='store_true',help='use EEcert for TLSA record (default)') parser.add_argument('--cacert', action='store_true',help='use CAcert for TLSA record (not supported yet)') parser.add_argument('--pubkey', action='store_true',help='use pubkey for TLSA record (not supported yet)') parser.add_argument('--txt', action='store_true',help='generate Kaminsky style TXT record (not supported yet)') parser.add_argument('--sha256', action='store_true',help='use SHA256 for the TLSA cert type') parser.add_argument('--sha512', action='store_true',help='use SHA512 for the TLSA cert type') parser.add_argument('--full', action='store_true',help='use full certificate for the TLSA cert type') # allow non-dnssec answers parser.add_argument('--insecure', action='store_true',help='allow use of non-dnssec answers to find SSL hosts') # limit networking to ipv4 or ipv6 only parser.add_argument('-4', dest='ipv4', action='store_true',help='use ipv4 networking only') parser.add_argument('-6', dest='ipv6', action='store_true',help='use ipv6 networking only') parser.add_argument('-q', '--quiet', action='store_true',help='suppress warnings and errors') parser.add_argument('-v', '--version', action='store_true',help='show version and exit') # finally, the host list parser.add_argument('hosts', metavar="hostname", nargs='+') args = parser.parse_args(sys.argv[1:]) if args.version: sys.exit("dane: version 1.2.2") if not args.rfc: args.draft = True if args.cacert: sys.exit("TLSA CAcert type record not yet supported") if args.pubkey: sys.exit("TLSA Pubkey type record not yet supported") if args.sha512: reftype=2 elif args.full: reftype=0 else: reftype=1 if args.quiet: quiet = True if args.tlsa: args.eecert = True if args.insecure: secure = False if args.ipv4 and not args.ipv6: transport = "ipv4" if args.ipv6 and not args.ipv4: transport = "ipv6" # filter the non-options arguments for an "@argument" and convert it for the axfr option. filterHost= [] if not args.nameserver: args.nameserver = [] for host in args.hosts: if host[0] == "@": args.nameserver.append(host[1:]) args.hosts.remove(host) args.axfr=True if args.rfc and args.draft: fmt = "both" elif args.rfc: fmt = "rfc" else: fmt = "draft" if not args.hosts: sys.exit("Host are needed.") # main("--help") for host in args.hosts: if host[-1] != ".": host += "." if not args.axfr: for host in args.hosts: print create_tlsa(host,1,reftype) if args.axfr: # Try and AXFR it if len(args.nameserver) == 0: sys.exit("nameserver needed. syntax: -n nameserver") resolver = ldnsx.resolver(args.nameserver[0], dnssec=True) for host in args.hosts: ipv4 = [] ipv6 = [] for rr in resolver.AXFR(host): if rr.rr_type() == "A" and rr.owner() not in ipv4: ipv4.append(rr.owner()) if rr.rr_type() == "AAAA" and rr.owner() not in ipv6: ipv6.append(rr.owner()) if transport != "ipv6": for host in ipv4: print create_tlsa(host,1,reftype) if transport != "ipv4": for host in ipv6: print create_tlsa(host,1,reftype) sshfp-1.2.2/COPYING0000644000175000017500000004312711631547701013244 0ustar julienjulien GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 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. 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 convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This 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 Library General Public License instead of this License. sshfp-1.2.2/tlsdns.usage.old0000644000175000017500000000576711631547701015333 0ustar julienjuliensecdns: Generate DNS records containing security information for various services like SSH, secdns [options] [options] [ [options]] secdns -n [options] [ [options]] (requires AXFR permission) -a attempt to generate SSHFP, IPSECKEY, TLSA and HASTLS records -D Insist all host DNS lookups are secured by an AD bit (trusted local resolver) (default is warn-only) Dynamic DNS options --update Propagate generated records via Dynamic DNS update --forward send an update for the forward zone based on FQDN --reverse send an update for the reverse zone based on IP[s] TLS DNS records ------------ --hastls Generate a HASTLS record [RFCxxxx] --with-fallback/--without-fallback Set the default fallback policy for all records if not specified per service (default is no fallback for web,imap/pop3, allowed fallback for smtp) --service-discover Generate HASTLS record based on probed services This only covers wellknown services (email & web) --service [--with-fallback] Add the service to the HASTLS list. Do not allow fallback per default [, ] For inline TLS services, specify twice. Examples: OR --service www --fallback-service http --service http,https - allow web traffic, prefer https, no fallback --service --with-fallback smtp,smtp - allow email with/without STARTTLS --raw Specify record in "generic dns record" format for older nameservers. uses RRTYPE #65280 (private use until IANA assigns the RRTYPE code) --tlsa Generate a TLSA record [RFCxxxx] --probe Obtain PKIX certificate or bare public key by connecting to the secure service --file Obtain PKIX certificate or bare public key from the specified file [formats?] use user@host:filename to specify a file over ssh --use-certhash The default - Use the same hashing algorithm as used within the certificate Only valid for PKIX certificate based TLS - ignored for bare public keys --use-hash Use instead of the hash used in the certificate. See IANA TLS hashnames at URL here for valid hashes --raw Specify record in "generic dns record" format for older nameservers. uses RRTYPE #65281 (private use until IANA assigns the RRTYPE code) --tlstxt Generate Kaminsky style TXT records for TLS service (freebird) IPSEC DNS records --ipseckey | --ipsectxt Generate IPSECKEY record [RFCxxxx] or TXT record [Openswan spec] [--file ] Obtain bare public key from a local file in ipsec.secrets(x) format [--showhostkey [[--host ] Obtain bare public key via the openswan "ipsec showhostkey" command. If hostname is specified, a login as root is attempted to run the command. --gateway Specify gateway IP (default: use host itself as gateway) --pref Specify preference (similar to MX records - default 10) SSH DNS records --sshfp Generate SSHFP records - see sshfp(1) sshfp-1.2.2/Makefile0000644000175000017500000000124011631547701013637 0ustar julienjulien# # A basic Makefile for sshfp # BIN = $(DESTDIR)/usr/bin MAN = $(DESTDIR)/usr/share/man/man1 all: man daneldnsx install: install -m 0755 -d $(BIN) install -m 0755 sshfp $(BIN) install -m 0755 dane $(BIN) install -m 0755 -d $(MAN) install -m 0644 sshfp.1 $(MAN) install -m 0644 dane.1 $(MAN) python -mcompileall daneldnsx.py gzip $(MAN)/sshfp.1 gzip $(MAN)/dane.1 sshfp.1: sshfp.1.xml xmlto man sshfp.1.xml dane.1: dane.1.xml xmlto man dane.1.xml daneldnsx: python -mcompileall daneldnsx.py man: man-page man-page: sshfp.1 dane.1 clean: -rm -f sshfp.1 dane.1 daneldnsx.pyc dist-clean: @echo Nothing to dist-clean - This is a python script sshfp-1.2.2/daneldnsx.py0000644000175000017500000006656711631547701014560 0ustar julienjulien#!/usr/bin/python # (c) Christopher Olah , 2011. Xelerance . """ Easy DNS (including DNSSEC) via ldns. ldns is a great library. It is a powerfull tool for working with DNS. python-ldns it is a straight up clone of the C interface, howver that is not a very good interface for python. Its documentation is incomplete and some functions don't work as described. And some objects don't have a full python API. ldnsx aims to fix this. It wraps around the ldns python bindings, working around its limitations and providing a well-documented, more pythonistic interface. **WARNING:** **API subject to change.** No backwards compatibility guarantee. Write software using this version at your own risk! Examples -------- Query the default resolver for google.com's A records. Print the response packet. >>> import ldnsx >>> resolver = ldnsx.resolver() >>> print resolver.query("google.com","A") Print the root NS records from f.root-servers.net; if we get a response, else an error message. >>> import ldnsx >>> pkt = ldnsx.resolver("f.root-servers.net").query(".", "NS") >>> if pkt: >>> for rr in pkt.answer(): >>> print rr >>> else: >>> print "response not received" """ import time, sys, calendar, warnings try: import ipcalc except ImportError: print >> sys.stderr, "ldnsx requires the python-ipcalc" print >> sys.stderr, "Fedora/CentOS: yum install python-ipcalc" print >> sys.stderr, "Debian/Ubuntu: apt-get install python-ipcalc" print >> sys.stderr, "openSUSE: zypper in python-ipcalc" sys.exit(1) try: import ldns except ImportError: print >> sys.stderr, "ldnsx requires the ldns-python sub-package from http://www.nlnetlabs.nl/projects/ldns/" print >> sys.stderr, "Fedora/CentOS: yum install ldns-python" print >> sys.stderr, "Debian/Ubuntu: apt-get install python-ldns" print >> sys.stderr, "openSUSE: zypper in python-ldns" sys.exit(1) __version__ = "-0.5" def isValidIP(ipaddr): try: bits = len(ipcalc.IP(ipaddr).bin()) except: return 0 if bits == 32: return 4 elif bits == 128: return 6 else: return 0 _rr_types={ "A" : ldns.LDNS_RR_TYPE_A, "A6" : ldns.LDNS_RR_TYPE_A6, "AAAA" : ldns.LDNS_RR_TYPE_AAAA, "AFSDB": ldns.LDNS_RR_TYPE_AFSDB, "ANY" : ldns.LDNS_RR_TYPE_ANY, "APL" : ldns.LDNS_RR_TYPE_APL, "ATMA" : ldns.LDNS_RR_TYPE_ATMA, "AXFR" : ldns.LDNS_RR_TYPE_AXFR, "CERT" : ldns.LDNS_RR_TYPE_CERT, "CNAME": ldns.LDNS_RR_TYPE_CNAME, "COUNT": ldns.LDNS_RR_TYPE_COUNT, "DHCID": ldns.LDNS_RR_TYPE_DHCID, "DLV" : ldns.LDNS_RR_TYPE_DLV, "DNAME": ldns.LDNS_RR_TYPE_DNAME, "DNSKEY": ldns.LDNS_RR_TYPE_DNSKEY, "DS" : ldns.LDNS_RR_TYPE_DS, "EID" : ldns.LDNS_RR_TYPE_EID, "FIRST": ldns.LDNS_RR_TYPE_FIRST, "GID" : ldns.LDNS_RR_TYPE_GID, "GPOS" : ldns.LDNS_RR_TYPE_GPOS, "HINFO": ldns.LDNS_RR_TYPE_HINFO, "IPSECKEY": ldns.LDNS_RR_TYPE_IPSECKEY, "ISDN" : ldns.LDNS_RR_TYPE_ISDN, "IXFR" : ldns.LDNS_RR_TYPE_IXFR, "KEY" : ldns.LDNS_RR_TYPE_KEY, "KX" : ldns.LDNS_RR_TYPE_KX, "LAST" : ldns.LDNS_RR_TYPE_LAST, "LOC" : ldns.LDNS_RR_TYPE_LOC, "MAILA": ldns.LDNS_RR_TYPE_MAILA, "MAILB": ldns.LDNS_RR_TYPE_MAILB, "MB" : ldns.LDNS_RR_TYPE_MB, "MD" : ldns.LDNS_RR_TYPE_MD, "MF" : ldns.LDNS_RR_TYPE_MF, "MG" : ldns.LDNS_RR_TYPE_MG, "MINFO": ldns.LDNS_RR_TYPE_MINFO, "MR" : ldns.LDNS_RR_TYPE_MR, "MX" : ldns.LDNS_RR_TYPE_MX, "NAPTR": ldns.LDNS_RR_TYPE_NAPTR, "NIMLOC": ldns.LDNS_RR_TYPE_NIMLOC, "NS" : ldns.LDNS_RR_TYPE_NS, "NSAP" : ldns.LDNS_RR_TYPE_NSAP, "NSAP_PTR" : ldns.LDNS_RR_TYPE_NSAP_PTR, "NSEC" : ldns.LDNS_RR_TYPE_NSEC, "NSEC3": ldns.LDNS_RR_TYPE_NSEC3, "NSEC3PARAMS" : ldns.LDNS_RR_TYPE_NSEC3PARAMS, "NULL" : ldns.LDNS_RR_TYPE_NULL, "NXT" : ldns.LDNS_RR_TYPE_NXT, "OPT" : ldns.LDNS_RR_TYPE_OPT, "PTR" : ldns.LDNS_RR_TYPE_PTR, "PX" : ldns.LDNS_RR_TYPE_PX, "RP" : ldns.LDNS_RR_TYPE_RP, "RRSIG": ldns.LDNS_RR_TYPE_RRSIG, "RT" : ldns.LDNS_RR_TYPE_RT, "SIG" : ldns.LDNS_RR_TYPE_SIG, "SINK" : ldns.LDNS_RR_TYPE_SINK, "SOA" : ldns.LDNS_RR_TYPE_SOA, "SRV" : ldns.LDNS_RR_TYPE_SRV, "SSHFP": ldns.LDNS_RR_TYPE_SSHFP, "TSIG" : ldns.LDNS_RR_TYPE_TSIG, "TXT" : ldns.LDNS_RR_TYPE_TXT, "UID" : ldns.LDNS_RR_TYPE_UID, "UINFO": ldns.LDNS_RR_TYPE_UINFO, "UNSPEC": ldns.LDNS_RR_TYPE_UNSPEC, "WKS" : ldns.LDNS_RR_TYPE_WKS, "X25" : ldns.LDNS_RR_TYPE_X25 } class resolver: """ A wrapper around ldns.ldns_resolver. **Examples** Making resolvers is easy! >>> from ldnsx import resolver >>> resolver() # from /etc/resolv.conf >>> resolver("") # resolver with no nameservers >>> resolver("193.110.157.135") #resolver pointing to ip addr >>> resolver("f.root-servers.net") # resolver pointing ip address(es) resolved from name >>> resolver("193.110.157.135, 193.110.157.136") >>> # resolver pointing to multiple ip addr, first takes precedence. So is playing around with their nameservers! >>> import ldnsx >>> res = ldnsx.resolver("192.168.1.1") >>> res.add_nameserver("192.168.1.2") >>> res.add_nameserver("192.168.1.3") >>> res.nameservers_ip() ["192.168.1.1","192.168.1.2","192.168.1.3"] And querying! >>> from ldnsx import resolver >>> res= resolver() >>> res.query("cow.com","A") ;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 7663 ;; flags: qr rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;; cow.com. IN A ;; ANSWER SECTION: cow.com. 300 IN A 208.87.34.18 ;; AUTHORITY SECTION: ;; ADDITIONAL SECTION: ;; Query time: 313 msec ;; SERVER: 192.168.111.9 ;; WHEN: Fri Jun 3 11:01:02 2011 ;; MSG SIZE rcvd: 41 """ def __init__(self, ns = None, dnssec = False, tcp = False, port = 53): """resolver constructor * ns -- the nameserver/comma delimited nameserver list defaults to settings from /etc/resolv.conf * dnssec -- should the resolver try and use dnssec or not? * tcp -- should the resolve try to connect with TCP? * port -- the port to use, must be the same for all nameservers """ # We construct based on a file and dump the nameservers rather than using # ldns_resolver_new() to avoid environment/configuration/magic specific # bugs. self._ldns_resolver = ldns.ldns_resolver.new_frm_file("/etc/resolv.conf") if ns != None: self.drop_nameservers() nm_list = ns.split(',') nm_list = map(lambda s: s.strip(), nm_list) nm_list = filter(lambda s: s != "", nm_list) nm_list.reverse() for nm in nm_list: self.add_nameserver(nm) self.set_dnssec(dnssec) self._ldns_resolver.set_usevc(tcp) self._ldns_resolver.set_port(port) def query(self, name, rr_type, rr_class="IN", flags=["RD"], tries = 3): """Run a query on the resolver. * name -- name to query for * rr_type -- the record type to query for * rr_class -- the class to query for, defaults to IN (Internet) * flags -- the flags to send the query with * tries -- the number of times to attempt to acheive query in case of packet loss, etc **Examples** Let's get some A records! >>> google_a_records = resolver.query("google.com","A").answer() Using DNSSEC is easy :) >>> dnssec_pkt = ldnsx.resolver(dnssec=True).query("xelerance.com") We let you use strings to make things easy, but if you prefer stay close to DNS... >>> AAAA = 28 >>> resolver.query("ipv6.google.com", AAAA) **More about rr_type** rr_type must be a supported resource record type. There are a large number of RR types: =========== =================================== ================== TYPE Value and meaning Reference =========== =================================== ================== A 1 a host address [RFC1035] NS 2 an authoritative name server [RFC1035] ... AAAA 28 IP6 Address [RFC3596] ... DS 43 Delegation Signer [RFC4034][RFC3658] ... DNSKEY 48 DNSKEY [RFC4034][RFC3755] ... Unassigned 32770-65279 Private use 65280-65534 Reserved 65535 =========== =================================== ================== (From http://www.iana.org/assignments/dns-parameters) RR types are given as a string (eg. "A"). In the case of Unassigned/Private use/Reserved ones, they are given as "TYPEXXXXX" where XXXXX is the number. ie. RR type 65280 is "TYPE65280". You may also pass the integer, but you always be given the string. If the version of ldnsx you are using is old, it is possible that there could be new rr_types that we don't recognise mnemonic for. You can still use the number XXX or the string "TYPEXXX". To determine what rr_type menmonics we support, please refer to resolver.supported_rr_types() """ if rr_type in _rr_types.keys(): _rr_type = _rr_types[rr_type] elif isinstance(rr_type,int): _rr_type = rr_type elif isinstance(rr_type,str) and rr_type[0:4] == "TYPE": try: _rr_type = int(rr_type[4:]) except: raise Exception("%s is a bad RR type. TYPEXXXX: XXXX must be a number") else: raise Exception("ldnsx (version %s) does not support the RR type %s." % (__version__, str(rr_type)) ) if rr_class == "IN": _rr_class = ldns.LDNS_RR_CLASS_IN elif rr_class == "CH": _rr_class = ldns.LDNS_RR_CLASS_CH elif rr_class == "HS": _rr_class = ldns.LDNS_RR_CLASS_HS else: raise Exception("ldnsx (version %s) does not support the RR class %s." % (__version__, str(rr_class)) ) _flags = 0 if "QR" in flags: _flags |= ldns.LDNS_QR if "AA" in flags: _flags |= ldns.LDNS_AA if "TC" in flags: _flags |= ldns.LDNS_TC if "RD" in flags: _flags |= ldns.LDNS_RD if "CD" in flags: _flags |= ldns.LDNS_CD if "RA" in flags: _flags |= ldns.LDNS_RA if "AD" in flags: _flags |= ldns.LDNS_AD if tries == 0: return None try: pkt = self._ldns_resolver.query(name, _rr_type, _rr_class, _flags) except KeyboardInterrupt: #Since so much time is spent waiting on ldns, this is very common place for Ctr-C to fall raise except: #Since the ldns exceptiion is not very descriptive... raise Exception("ldns backend ran into problems. Likely, the name you were querying for, %s, was invalid." % name) if not pkt: if tries <= 1: return None else: time.sleep(1) self = resolver( ",".join(self.nameservers_ip()) ) return self.query(name, rr_type, rr_class=rr_class, flags=flags, tries = tries-1) return packet(pkt) #ret = [] #for rr in pkt.answer().rrs(): # ret.append([str(rr.owner()),rr.ttl(),rr.get_class_str(),rr.get_type_str()]+[str(rdf) for rdf in rr.rdfs()]) #return ret def suported_rr_types(self): """ Returns the supported DNS resource record types. Refer to resolver.query() for thorough documentation of resource record types or refer to: http://www.iana.org/assignments/dns-parameters """ return _rr_types.keys() def AXFR(self,name): """AXFR for name * name -- name to AXFR for This function is a generator. As it AXFRs it will yield you the records. **Example** Let's get a list of the tlds (gotta catch em all!): >>> tlds = [] >>> for rr in resolver("f.root-servers.net").AXFR("."): >>> if rr.rr_type() == "NS": >>> tlds.append(rr.owner()) """ #Dname seems to be unecessary on some computers, but it is on others. Avoid bugs. if self._ldns_resolver.axfr_start(ldns.ldns_dname(name), ldns.LDNS_RR_CLASS_IN) != ldns.LDNS_STATUS_OK: raise Exception("Starting AXFR failed. Error: %s" % ldns.ldns_get_errorstr_by_id(status)) pres = self._ldns_resolver.axfr_next() while pres: yield resource_record(pres) pres = self._ldns_resolver.axfr_next() def nameservers_ip(self): """ returns a list of the resolvers nameservers (as IP addr) """ nm_stack2 =[] nm_str_stack2=[] nm = self._ldns_resolver.pop_nameserver() while nm: nm_stack2.append(nm) nm_str_stack2.append(str(nm)) nm = self._ldns_resolver.pop_nameserver() for nm in nm_stack2: self._ldns_resolver.push_nameserver(nm) nm_str_stack2.reverse() return nm_str_stack2 def add_nameserver(self,ns): """ Add a nameserver, IPv4/IPv6/name. """ if isValidIP(ns) == 4: address = ldns.ldns_rdf_new_frm_str(ldns.LDNS_RDF_TYPE_A,ns) self._ldns_resolver.push_nameserver(address) elif isValidIP(ns) == 6: address = ldns.ldns_rdf_new_frm_str(ldns.LDNS_RDF_TYPE_AAAA,ns) self._ldns_resolver.push_nameserver(address) else: resolver = ldns.ldns_resolver.new_frm_file("/etc/resolv.conf") #address = resolver.get_addr_by_name(ns) address = resolver.get_addr_by_name(ldns.ldns_dname(ns)) if not address: address = resolver.get_addr_by_name(ldns.ldns_dname(ns)) if not address: raise Exception("Failed to resolve address for %s" % ns) for rr in address.rrs(): self._ldns_resolver.push_nameserver_rr(rr) def drop_nameservers(self): """Drops all nameservers. This function causes the resolver to forget all nameservers. """ while self._ldns_resolver.pop_nameserver(): pass def set_nameservers(self, nm_list): """Takes a list of nameservers and sets the resolver to use them """ self.drop_nameservers() for nm in nm_list: self.add_nameserver(nm) def __repr__(self): return "" % ", ".join(self.nameservers_ip()) __str__ = __repr__ def set_dnssec(self,new_dnssec_status): """Set whether the resolver uses DNSSEC. """ self._ldns_resolver.set_dnssec(new_dnssec_status) def query(name, rr_type, rr_class="IN", flags=["RD"], tries = 1): """Convenience function. Creates a resolver and then queries it. Refer to resolver.query() """ res = resolver() return res.query(name, rr_type, rr_class, flags, tries) def get_rrs(name, rr_type, rr_class="IN", tries = 3, strict = False, **kwds): """Convenience function. Gets RRs for name of type rr_type trying tries times. If strict, it raises and exception on failure, otherwise it returns []. """ res = resolver() if "|" in rr_type: pkt = res.query(name, "ANY", rr_class=rr_class, tries=tries) else: pkt = res.query(name, rr_type, rr_class=rr_class, tries=tries) if pkt: return pkt.answer(rr_type=rr_type, **kwds) else: if strict: raise Exception("LDNS couldn't complete query") else: return [] def secure_query(name, rr_type, rr_class="IN", flags=["RD"], tries = 1, flex=False): res = resolver(dnssec=True) pkt = res.query(name, rr_type, rr_class, flags, tries) if pkt.rcode() == "SERVFAIL": raise Exception("%s lookup failed (server error or dnssec validation failed)" % name) if pkt.rcode() == "NXDOMAIN": if "AD" in pkt.flags(): raise Exception("%s lookup failed (non-existence proven by DNSSEC)" % hostname ) else: raise Exception("%s lookup failed" % hostname ) if pkt.rcode() == "NOERROR": if "AD" not in pkt.flags(): if not flex: raise Exception("DNS lookup was insecure") else: warnings.warn("DNS lookup was insecure") return pkt else: raise Exception("unknown ldns error, %s" % pkt.rcode()) class packet: def _construct_rr_filter(self, **kwds): def match(pattern, target): if pattern[0] in ["<",">","!"]: rel = pattern[0] pattern=pattern[1:] elif pattern[0:2] in ["<=","=>"]: rel = pattern[0:2] pattern=pattern[2:] else: rel = "=" for val in pattern.split("|"): if {"<" : target < val, ">" : target > val, "!" : target != val, "=" : target == val, ">=": target >= val, "<=": target <= val}[rel]: return True return False def f(rr): for key in kwds.keys(): if ( ( isinstance(kwds[key], list) and str(rr[key]) not in map(str,kwds[key]) ) or ( not isinstance(kwds[key], list) and not match(str(kwds[key]), str(rr[key])))): return False return True return f def __init__(self, pkt): self._ldns_pkt = pkt def __repr__(self): return str(self._ldns_pkt) __str__ = __repr__ def rcode(self): """Returns the rcode. Example returned value: "NOERROR" possilbe rcodes (via ldns): "FORMERR", "MASK", "NOERROR", "NOTAUTH", "NOTIMPL", "NOTZONE", "NXDOMAIN", "NXRSET", "REFUSED", "SERVFAIL", "SHIFT", "YXDOMAIN", "YXRRSET" Refer to http://www.iana.org/assignments/dns-parameters section: DNS RCODEs """ return self._ldns_pkt.rcode2str() def opcode(self): """Returns the rcode. Example returned value: "QUERY" """ return self._ldns_pkt.opcode2str() def flags(self): """Return packet flags (as list of strings). Example returned value: ['QR', 'RA', 'RD'] **What are the flags?** ======== ==== ===================== ========= Bit Flag Description Reference ======== ==== ===================== ========= bit 5 AA Authoritative Answer [RFC1035] bit 6 TC Truncated Response [RFC1035] bit 7 RD Recursion Desired [RFC1035] bit 8 RA Recursion Allowed [RFC1035] bit 9 Reserved bit 10 AD Authentic Data [RFC4035] bit 11 CD Checking Disabled [RFC4035] ======== ==== ===================== ========= (from http://www.iana.org/assignments/dns-parameters) There is also QR. It is mentioned in other sources, though not the above page. It being false means that the packet is a query, it being true means that it is a response. """ ret = [] if self._ldns_pkt.aa(): ret += ["AA"] if self._ldns_pkt.ad(): ret += ["AD"] if self._ldns_pkt.cd(): ret += ["CD"] if self._ldns_pkt.qr(): ret += ["QR"] if self._ldns_pkt.ra(): ret += ["RA"] if self._ldns_pkt.rd(): ret += ["RD"] if self._ldns_pkt.tc(): ret += ["TC"] return ret def answer(self, **filters): """Returns the answer section. * **filters -- a filtering mechanism Since a very common desire is to filter the resource records in a packet section, we provide a special tool for doing this: filters. They are a lot like regular python filters, but more convenient. If you set a field equal to some value, you will only receive resource records for which it holds true. **Examples** >>> res = ldnsx.resolver() >>> pkt = res.query("google.ca","A") >>> pkt.answer() [google.ca. 28 IN A 74.125.91.99 , google.ca. 28 IN A 74.125.91.105 , google.ca. 28 IN A 74.125.91.147 , google.ca. 28 IN A 74.125.91.103 , google.ca. 28 IN A 74.125.91.104 , google.ca. 28 IN A 74.125.91.106 ] To understand filters, comsider the following: >>> res = ldnsx.resolver() >>> pkt = res.query("google.ca","ANY") >>> pkt.answer() [google.ca. 284 IN MX 10 google.com.s9b1.psmtp.com. , google.ca. 284 IN MX 10 google.com.s9a1.psmtp.com. , google.ca. 284 IN MX 10 google.com.s9a2.psmtp.com. , google.ca. 284 IN MX 10 google.com.s9b2.psmtp.com. , google.ca. 4 IN SOA ns1.google.com. dns-admin.google.com. 1452303 21600 3600 1209600 300 , google.ca. 134530 IN NS ns1.google.com. , google.ca. 134530 IN NS ns2.google.com. , google.ca. 134530 IN NS ns4.google.com. , google.ca. 134530 IN NS ns3.google.com. , google.ca. 261 IN A 74.125.91.103 , google.ca. 261 IN A 74.125.91.99 , google.ca. 261 IN A 74.125.91.147 , google.ca. 261 IN A 74.125.91.105 , google.ca. 261 IN A 74.125.91.106 , google.ca. 261 IN A 74.125.91.104 ] >>> pkt.answer(rr_type="NS") [google.ca. 134530 IN NS ns1.google.com. , google.ca. 134530 IN NS ns2.google.com. , google.ca. 134530 IN NS ns4.google.com. , google.ca. 134530 IN NS ns3.google.com. ] fields are the same as when indexing a resource record. If you want to allow a feild to be multiple values, you may use "A|B|C.." format. Using lists is depricated. """ ret = [resource_record(rr) for rr in self._ldns_pkt.answer().rrs()] return filter(self._construct_rr_filter(**filters), ret) def authority(self, **filters): """Returns the authority section. * **filters -- a filtering mechanism Since a very common desire is to filter the resource records in a packet section, we provide a special tool for doing this: filters. They are a lot like regular python filters, but more convenient. If you set a field equal to some value, you will only receive resource records for which it holds true. **Examples** >>> res = ldnsx.resolver() >>> pkt = res.query("google.ca","A") >>> pkt.authority() [google.ca. 251090 IN NS ns3.google.com. , google.ca. 251090 IN NS ns1.google.com. , google.ca. 251090 IN NS ns2.google.com. , google.ca. 251090 IN NS ns4.google.com. ] """ ret = [resource_record(rr) for rr in self._ldns_pkt.authority().rrs()] return filter(self._construct_rr_filter(**filters), ret) def additional(self, **filters): """Returns the additional section. * **filters -- a filtering mechanism Since a very common desire is to filter the resource records in a packet section, we provide a special tool for doing this: filters. They are a lot like regular python filters, but more convenient. If you set a field equal to some value, you will only receive resource records for which it holds true. **Examples** >>> res = ldnsx.resolver() >>> pkt = res.query("google.ca","A") >>> pkt.additional() [ns3.google.com. 268778 IN A 216.239.36.10 , ns1.google.com. 262925 IN A 216.239.32.10 , ns2.google.com. 255659 IN A 216.239.34.10 , ns4.google.com. 264489 IN A 216.239.38.10 ] """ ret = [resource_record(rr) for rr in self._ldns_pkt.additional().rrs()] return filter(self._construct_rr_filter(**filters), ret) def question(self, **filters): """Returns the question section. * **filters -- a filtering mechanism Since a very common desire is to filter the resource records in a packet section, we provide a special tool for doing this: filters. They are a lot like regular python filters, but more convenient. If you set a field equal to some value, you will only receive resource records for which it holds true. """ ret = [resource_record(rr) for rr in self._ldns_pkt.question().rrs()] return filter(self._construct_rr_filter(**filters), ret) class resource_record: _rdfs = None _iter_pos = None def __init__(self, rr): self._ldns_rr = rr self._rdfs = [str(rr.owner()),rr.ttl(),rr.get_class_str(),rr.get_type_str()]+[str(rdf) for rdf in rr.rdfs()] def __repr__(self): return str(self._ldns_rr) __str__ = __repr__ def __iter__(self): self._iter_pos = 0 return self def next(self): if self._iter_pos < len(self._rdfs): self._iter_pos += 1 return self._rdfs[self._iter_pos-1] else: raise StopIteration def __getitem__(self, n): if isinstance(n, int): return self._rdfs[n] elif isinstance(n, str): n = n.lower() if n in ["owner"]: return self.owner() elif n in ["rr_type", "rr type", "type"]: return self.rr_type() elif n in ["rr_class", "rr class", "class"]: return self.rr_class() elif n in ["covered_type", "covered type", "type2"]: return self.covered_type() elif n in ["ttl"]: return self.ttl() elif n in ["ip"]: return self.ip() elif n in ["alg", "algorithm"]: return self.alg() elif n in ["protocol"]: return self.protocol() elif n in ["flags"]: return self.flags() else: raise Exception("ldnsx (version %s) does not recognize the rr field %s" % (__version__,n) ) else: raise TypeError("bad type %s for index resource record" % type(n) ) #def rdfs(self): # return self._rdfs.clone() def owner(self): return str(self._ldns_rr.owner()) def rr_type(self): return self._ldns_rr.get_type_str() def covered_type(self): if self.rr_type() == "RRSIG": return self[4] else: return "" def rr_class(self): return self._ldns_rr.get_class_str() def ttl(self): return self._ldns_rr.ttl() def inception(self, out_format="UTC"): """returns the inception time in format out_format, defaulting to a UTC string. options for out_format are: UTC -- a UTC string eg. 20110712192610 (2011/07/12 19:26:10) unix -- number of seconds since the epoch, Jan 1, 1970 struct_time -- the format used by python's time library """ # Something very strange is going on with inception/expiration dates in DNS. # According to RFC 4034 section 3.1.5 (http://tools.ietf.org/html/rfc4034#page-9) # the inception/expiration fields should be in seconds since Jan 1, 1970, the Unix # epoch (as is standard in unix). Yet all the packets I've seen provide UTC encoded # as a string instead, eg. "20110712192610" which is 2011/07/12 19:26:10. # # It turns out that this is a standard thing that ldns is doing before the data gets # to us. if self.rr_type() == "RRSIG": if out_format.lower() in ["utc", "utc str", "utc_str"]: return self[9] elif out_format.lower() in ["unix", "posix", "ctime"]: return calendar.timegm(time.strptime(self[9], "%Y%m%d%H%M%S")) elif out_format.lower() in ["relative"]: return calendar.timegm(time.strptime(self[9], "%Y%m%d%H%M%S")) - time.time() elif out_format.lower() in ["struct_time", "time.struct_time"]: return time.strptime(self[9], "%Y%m%d%H%M%S") else: raise Exception("unrecognized time format") else: return "" def expiration(self, out_format="UTC"): """get expiration time. see inception() for more information""" if self.rr_type() == "RRSIG": if out_format.lower() in ["utc", "utc str", "utc_str"]: return self[8] elif out_format.lower() in ["unix", "posix", "ctime"]: return calendar.timegm(time.strptime(self[8], "%Y%m%d%H%M%S")) elif out_format.lower() in ["relative"]: return calendar.timegm(time.strptime(self[8], "%Y%m%d%H%M%S")) - time.time() elif out_format.lower() in ["struct_time", "time.struct_time"]: return time.strptime(self[8], "%Y%m%d%H%M%S") else: raise Exception("unrecognized time format") else: return "" def inception_unix(self): """ depricated -- use inception("unix")""" if self.rr_type() == "RRSIG": s = self[9] if s[:2] == "20": return calendar.timegm(time.strptime(s, "%Y%m%d%H%M%S")) else: return int(s) else: return -1 def expiration_unix(self): """depricated -- use expiration("unix") please""" if self.rr_type() == "RRSIG": s = self[8] if s[:2] == "20": return calendar.timegm(time.strptime(s, "%Y%m%d%H%M%S")) else: return int(s) else: return -1 def ip(self): if self.rr_type() in ["A", "AAAA"]: return self[4] else: raise Exception("ldnsx does not support ip for records other than A/AAAA") def alg(self): t = self.rr_type() if t == "RRSIG": return int(self[5]) elif t == "DNSKEY": return int(self[6]) elif t == "DS": return int(self[5]) else: return -1 def protocol(self): t = self.rr_type() if t == "DNSKEY": return int(self[5]) else: return -1 def flags(self): t = self.rr_type() if t == "DNSKEY": ret = [] n = int(self[4]) for m in range(1): if 2**(15-m) & n: if m == 7: ret.append("ZONE") elif m == 8: ret.append("REVOKE") elif m ==15: ret.append("SEP") else: ret.append(m) return ret else: return [] sshfp-1.2.2/README0000644000175000017500000000124011631547701013057 0ustar julienjulienSoftware : sshfp URL : http://www.xelerance.com/software/sshfp/ Source : ftp://ftp.xelerance.com/sshfp/ License : GPLv2+ Mailinglist : http://lists.xelerance.com/mailman/listinfo/sshfp/ Authors : Paul Wouters, Chrisopher Olah Summary : Generate RFC-4255 SSHFP DNS records from known_hosts files or ssh-keyscan sshfp generates DNS SSHFP records from SSH public keys. sshfp can take public keys from a known_hosts file or from scanning the host's sshd daemon. The ssh client can use these SSHFP records if you set "VerifyHostKeyDNS yes" in the file /etc/ssh/ssh_config or ~/.ssh/config. See RFC-4255 REQUIREMENTS sshfp requires python-dns: http://www.pythondns.org