sshfp-1.2.2/ 0000755 0001750 0001750 00000000000 11631547701 012202 5 ustar julien julien sshfp-1.2.2/dane.1 0000644 0001750 0001750 00000012522 11631547701 013175 0 ustar julien julien '\" 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.xml 0000644 0001750 0001750 00000017046 11631547701 014002 0 ustar julien julien
April 12, 2011dane1April 12, 2011Paul WoutersInternet / DNSdaneGenerate TLSA/HASTLS DNS records by scanning SSL/TLS sitesSYNTAXdane [] [] [] []
[|] [] []
[] [] [] []
[] [] [] []
[] [ <nameserver>]
host1 [host2 ...]]
[@nameserver]]
DESCRIPTIONdane 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 querySupress all warnings - useful when scanning lots of host where some do not run SSLUse 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) recordsOutput TLSA record reference type 0 (full cert) recordsOutput 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 DNSSECOnly use ipv4 networking - do not attempt to connect to AAAA SSL sitesOnly use ipv6 networking - do not attempt to connect to A SSL sitesOutput help information and exit.Output version information and exit.FILES~/.ssh/known_hostsREQUIREMENTSdane requires python-dns and python-argparse(http://www.pythondns.org)Fedora: yum install python-dns python-argparseDebian: apt-get install python-dnspython python-argparseBUGSI'm sure there areEXAMPLEStypical usage:dane www.xelerance.comdane --rfc --sha512 www.xelerance.com dane --insecure --draft xelerance.com @ns0.xelerance.netSEE ALSOsshfp1ssh1 and RFC-XXXXhttp://www.xelerance.com/software/sshfp/http://lists.xelerance.com/mailman/listinfo/sshfp/AUTHORSPaul Wouters <paul@xelerance.com>COPYRIGHTCopyright 2011 Xelerance CorporationThis 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/ 0000755 0001750 0001750 00000000000 11631547701 013442 5 ustar julien julien sshfp-1.2.2/fedora/sshfp.spec 0000644 0001750 0001750 00000004502 11631547701 015442 0 ustar julien julien %{!?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/CHANGES 0000644 0001750 0001750 00000006015 11631547701 013177 0 ustar julien julien v1.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.xml 0000644 0001750 0001750 00000016460 11631547701 014215 0 ustar julien julien
April 12, 2011sshfp1April 12, 2011Paul WoutersInternet / DNSsshfpGenerate SSHFP DNS records from knownhosts files or ssh-keyscanSYNTAXsshfp [ <knownhosts_file>] [] [] | [<host1> [host2 ...]]
sshfp [ <port>] [] <> [>] <domain1> [domain2] | <host1> [host2 ...] >DESCRIPTIONsshfp 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-keyscanknownhosts_file> <hostname1> [hostname2 ...]Obtain public SSH keys from a known_hosts file. Defaults to using ~/.ssh/known_hostsScan 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 stderrFILES~/.ssh/known_hostsREQUIREMENTSsshfp requires python-dns (http://www.pythondns.org)Fedora: yum install python-dnsDebian: apt-get install python-dnspythonBUGSif 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 presentEXAMPLEStypical 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.txtsshfp -a -d -d xelerance.com -n ns0.xelerance.net >> /var/named/primary/xelerance.comSEE ALSOssh-keyscan1ssh1 and RFC-4255http://www.xelerance.com/software/sshfp/http://lists.xelerance.com/mailman/listinfo/sshfp/AUTHORSPaul Wouters <paul@xelerance.com>, Jacob Appelbaum <jacob@appelbaum.net>, James Brown <jbrown@yelp.com>COPYRIGHTCopyright 2006-2010 Xelerance CorporationThis 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/BUGS 0000644 0001750 0001750 00000002201 11631547701 012660 0 ustar julien julien KNOWN 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.1 0000644 0001750 0001750 00000013253 11631547701 013413 0 ustar julien julien '\" 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-unused 0000644 0001750 0001750 00000007713 11631547701 015463 0 ustar julien julien import 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/sshfp 0000755 0001750 0001750 00000025405 11631547701 013261 0 ustar julien julien #!/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/TODO 0000644 0001750 0001750 00000000137 11631547701 012673 0 ustar julien julien - support ixfr with serial to allow to receive only changes since.... dig supports this option
sshfp-1.2.2/dane 0000755 0001750 0001750 00000021413 11631547701 013040 0 ustar julien julien #!/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/COPYING 0000644 0001750 0001750 00000043127 11631547701 013244 0 ustar julien julien 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.old 0000644 0001750 0001750 00000005767 11631547701 015333 0 ustar julien julien secdns: 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/Makefile 0000644 0001750 0001750 00000001240 11631547701 013637 0 ustar julien julien #
# 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.py 0000644 0001750 0001750 00000066567 11631547701 014560 0 ustar julien julien #!/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/README 0000644 0001750 0001750 00000001240 11631547701 013057 0 ustar julien julien Software : 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