diff --git a/CHANGES b/CHANGES
index bc741a3..a6b4bf8 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,4 +1,19 @@
-v2.7 (Jan 2, 2015)
+v3.1 (January 14, 2020)
+- tlsa: bug fixes [Peter van Dijk, Dirk Stöcker]
+- tlsa: separate duplicate output due to multiple IP addresses [Dirk Stöcker]
+- openpgpkey: update docs [Arsen Stasic]
+- sshfp: Support for xmss [Kishan Takoordyal]
+
+v3.0 (November 3, 2019)
+- upgrade to python version 3 [Frank Crawford]
+
+v2.8 (April 13, 2019)
+- tlsa: add submission port (587) to starttls support [Dirk Stöcker]
+- tlsa: fixes for newer m2crypto [Dirk Stöcker]
+- tlsa: small fixes in error handling [Dirk Stöcker]
+- documentation fixes [Jakub Wilk]
+
+v2.7 (Jan 2, 2016)
- openpgpkey: Add --keyid option to select key on keyid [Paul]
- openpgpkey: fix --rootanchor option handling [Ondřej Surý]
- openpgpkey: print keyid as bind zone style comment [Carsten Strotmann]
@@ -8,7 +23,7 @@ v2.7 (Jan 2, 2015)
- tlsa: Support --starttls for imap, pop3, smtp and ftp [Dirk Stöcker]
- Remove DLV which is being sunset [Paul]
-v2.6 (Jan 6, 2015))
+v2.6 (Jan 6, 2015)
- openpgpkey: Added --fetch option to fetch a public key from DNS [Paul]
- openpgpkey: Update rrtype from private use to IANA allocation (#61) [Paul]
- openpgpkey: Remove no longer needed --rrtype option [Paul]
diff --git a/README b/README
index a77329a..7a540e1 100644
--- a/README
+++ b/README
@@ -5,8 +5,7 @@ This package contains various tools to generate special DNS records
sshfp Generate RFC-4255 SSHFP DNS records from known_hosts
files or ssh-keyscan
tlsa Generate RFC-6698 TLSA DNS records via TLS
-openpgpkey Generate draft-ietf-dane-openpgpkey-01 OPENPGPKEY DNS
- records using gpg
+openpgpkey Generate RFC-7929 OPENPGPKEY DNS records using gpg
ipseckey Generate RFC-4025 IPSECKEY DNS records on Libreswan
IPsec servers
@@ -25,14 +24,13 @@ Gerald Turner <gturner@unzane.com>
Ondřej Surý <ondrej@sury.org>
Jan Vcelak <jv@fcelda.cz>
Dirk Stöcker <hashslinger@dstoecker.de>
+Frank Crawford <frank@crawford.emu.id.au>
REQUIREMENTS:
-python-dns http://www.pythondns.org/
+python-dns http://www.dnspython.org/
python-gnupg http://pythonhosted.org/python-gnupg/
-python-ipaddr
-python-argparse (for python < 2.7)
-M2Crypto http://chandlerproject.org/Projects/MeTooCrypto
+m2crypto http://gitlab.com/m2crypto/m2crypto/
unbound-python http://www.unbound.net/
ssh-keygen from openssh
gpg from gnupg
diff --git a/debian/changelog b/debian/changelog
index 202517b..b885da5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,22 @@
+hash-slinger (3.1-1.1) unstable; urgency=low
+
+ * Non-maintainer upload.
+ * Remove the unused dependency on python3-ipaddr. (Closes: #1003578)
+ * debian/control: Add Homepage.
+
+ -- Adrian Bunk <bunk@debian.org> Thu, 10 Feb 2022 08:03:46 +0200
+
+hash-slinger (3.1-1) unstable; urgency=medium
+
+ * Update d/control (Closes: #899533)
+ * Update d/watch, update d/gbp.conf
+ * New upstream version 3.1 (Closes: #775048, #780498, #738494)
+ * d/control: Port to python3 (Closes: #927132, #834971, #738490
+ * Add ${python3:Depends} to Depends
+ * Final touches to d/control: policy version, priority optional
+
+ -- Ondřej Surý <ondrej@debian.org> Sun, 14 Feb 2021 17:40:02 +0100
+
hash-slinger (2.7-1) unstable; urgency=medium
* Imported Upstream version 2.7 (Closes: #817894)
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index 45a4fb7..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-8
diff --git a/debian/control b/debian/control
index 3fa6196..c6d5fed 100644
--- a/debian/control
+++ b/debian/control
@@ -1,11 +1,14 @@
Source: hash-slinger
Section: utils
-Priority: extra
+Priority: optional
Maintainer: Debian DNS Team <team+dns@tracker.debian.org>
Uploaders: Ondřej Surý <ondrej@debian.org>
Build-Depends: debhelper-compat (= 12),
+ dh-python,
+ python3,
xmlto
-Standards-Version: 3.9.5
+Standards-Version: 4.5.1
+Homepage: https://github.com/letoams/hash-slinger
Vcs-Git: https://salsa.debian.org/dns-team/hash-slinger.git
Vcs-Browser: https://salsa.debian.org/dns-team/hash-slinger
@@ -14,14 +17,12 @@ Architecture: any
Depends: ca-certificates,
dns-root-data,
openssh-client,
- python,
- python-argparse,
- python-dnspython,
- python-gnupg,
- python-ipaddr,
- python-m2crypto,
- python-unbound,
+ python3-dnspython,
+ python3-gnupg (>= 0.3.7),
+ python3-m2crypto (>= 0.24.0),
+ python3-unbound,
${misc:Depends},
+ ${python3:Depends},
${shlibs:Depends}
Replaces: sshfp
Conflicts: sshfp
diff --git a/debian/patches/0001-Debian-default-root.key-resides-in-usr-share-dns-roo.patch b/debian/patches/0001-Debian-default-root.key-resides-in-usr-share-dns-roo.patch
index c237088..f5f8b76 100644
--- a/debian/patches/0001-Debian-default-root.key-resides-in-usr-share-dns-roo.patch
+++ b/debian/patches/0001-Debian-default-root.key-resides-in-usr-share-dns-roo.patch
@@ -1,4 +1,4 @@
-From: =?utf-8?q?Ond=C5=99ej_Sur=C3=BD?= <ondrej@sury.org>
+From: =?utf-8?b?T25kxZllaiBTdXLDvQ==?= <ondrej@sury.org>
Date: Fri, 11 Mar 2016 14:37:51 +0100
Subject: Debian default root.key resides in /usr/share/dns/root.key
@@ -8,7 +8,7 @@ Subject: Debian default root.key resides in /usr/share/dns/root.key
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/openpgpkey b/openpgpkey
-index e7ca0e6..00b3c84 100755
+index e423817..d3b80ef 100755
--- a/openpgpkey
+++ b/openpgpkey
@@ -95,7 +95,7 @@ if __name__ == '__main__':
@@ -21,7 +21,7 @@ index e7ca0e6..00b3c84 100755
parser.add_argument('--uid', action='store', default='', help='override the uid (email address) within the key')
@@ -125,7 +125,7 @@ if __name__ == '__main__':
- print >> sys.stdout, 'openpgpkey: %s is not a file. Unable to use it as rootanchor' % args.rootanchor
+ print('openpgpkey: %s is not a file. Unable to use it as rootanchor' % args.rootanchor, file=sys.stdout)
sys.exit(1)
else:
- cauldron = ( "/var/lib/unbound/root.anchor", "/var/lib/unbound/root.key", "/etc/unbound/root.key" )
@@ -30,11 +30,11 @@ index e7ca0e6..00b3c84 100755
if os.path.isfile(root):
rootanchor=root
diff --git a/tlsa b/tlsa
-index 65c4d60..ff8c6b3 100755
+index ae925ea..1c6c949 100755
--- a/tlsa
+++ b/tlsa
@@ -37,7 +37,7 @@ from hashlib import sha256, sha512
- from ipaddr import IPv4Address, IPv6Address
+ from ipaddress import IPv4Address, IPv6Address
ROOTKEY="none"
-cauldron = ( "/var/lib/unbound/root.anchor", "/var/lib/unbound/root.key", "/etc/unbound/root.key" )
diff --git a/debian/rules b/debian/rules
index 57e590a..a134445 100755
--- a/debian/rules
+++ b/debian/rules
@@ -10,7 +10,7 @@
#export DH_VERBOSE=1
%:
- dh $@
+ dh $@ --with python3
override_dh_installchangelogs:
dh_installchangelogs CHANGES
diff --git a/fedora/hash-slinger.spec b/fedora/hash-slinger.spec
index 2640543..a85b9a3 100644
--- a/fedora/hash-slinger.spec
+++ b/fedora/hash-slinger.spec
@@ -1,15 +1,16 @@
Summary: Generate and verify various DNS records such as SSHFP, TLSA and OPENPGPKEY
Name: hash-slinger
-Version: 2.6
+Version: 3.1
Release: 1%{?dist}
License: GPLv2+
Url: http://people.redhat.com/pwouters/%{name}/
Source: http://people.redhat.com/pwouters/%{name}/%{name}-%{version}.tar.gz
-Group: Applications/Internet
# Only to regenerate the man page, which is shipped per default
# Buildrequires: xmlto
-Requires: python-dns, python-argparse, unbound-python, python-ipaddr, python-gnupg >= 0.3.5-2
-Requires: openssh-clients >= 4, m2crypto
+BuildRequires: python3-devel
+Requires: python3 >= 3.4
+Requires: python3-dns, python3-unbound
+Requires: openssh-clients >= 4, python3-m2crypto, python3-gnupg >= 0.3.7
BuildArch: noarch
Obsoletes: sshfp < 2.0
Provides: sshfp = %{version}
@@ -17,10 +18,13 @@ Provides: sshfp = %{version}
%description
This package contains various tools to generate special DNS records:
-sshfp Generate RFC-4255 SSHFP DNS records from known_hosts or ssh-keyscan
-tlsa Generate RFC-6698 TLSA DNS records via TLS
-openpgpkey Generate RFC-<TBD> OPENPGPKEY DNS records
-ipseckey Generate RFC-4025 IPSECKEY DNS records on Libreswan IPsec servers
+sshfp Generate RFC-4255 SSHFP DNS records from known_hosts files
+ or ssh-keyscan
+tlsa Generate RFC-6698 TLSA DNS records via TLS
+openpgpkey Generate draft-ietf-dane-openpgpkey DNS records from OpenPGP
+ keyrings
+ipseckey Generate RFC-4025 IPSECKEY DNS records on Libreswan
+ IPsec servers
This package has incorporated the old 'sshfp' and 'swede' commands/packages
@@ -34,15 +38,25 @@ make all
rm -rf ${RPM_BUILD_ROOT}
make DESTDIR=${RPM_BUILD_ROOT} install
-%clean
-rm -rf ${RPM_BUILD_ROOT}
-
%files
%doc BUGS CHANGES README COPYING
%{_bindir}/*
%doc %{_mandir}/man1/*
%changelog
+* Wed Sep 21 2016 Paul Wouters <pwouters@redhat.com> - 2.7-3
+- Remove Requires: for python-argparse which is now part of python core
+
+* Sun Jan 03 2016 Paul Wouters <pwouters@redhat.com> - 2.7-1
+- Updated to 2.7 (updates to latest IETF drafts and RFCs, STARTTLS support)
+
+* Tue Jan 06 2015 Paul Wouters <pwouters@redhat.com> - 2.6-1
+- Updated to 2.6. Adds ipseckey, bugfixes for sshfp and openpgpkey
+
+* Sat Jan 18 2014 Paul Wouters <pwouters@redhat.com> - 2.5-1
+- Update to 2.5 which has OPENPGPKEY (draft 02) support
+- Added python-gnupg requires
+
* Mon Jan 6 2014 Paul Wouters <pwouters@redhat.com> - 2.4-1
- Updated to 2.4 which updates OPENPGPKEY support
@@ -50,7 +64,7 @@ rm -rf ${RPM_BUILD_ROOT}
- Updated to 2.3 which adds support for OPENPGPKEY
* Mon Jun 24 2013 Paul Wouters <pwouters@redhat.com> - 2.2-1
-- New version
+- Updated to 2.2 which fixes tsla usage 0 and --ipv4/--ipv6 options
* Sat Sep 15 2012 Paul Wouters <pwouters@redhat.com> - 2.1-1
- Updated COPYING to properly reflect GPLv2 "or later"
diff --git a/ipseckey b/ipseckey
index f6158ad..a61f455 100755
--- a/ipseckey
+++ b/ipseckey
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# Show IPSECKEY records generated by libreswan IPsec
# might work on some versions of older openswan as well
#
@@ -18,7 +18,7 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-VERSION = "2.7"
+VERSION = "3.1"
import os
import sys
@@ -32,11 +32,11 @@ ipsec = "/usr/sbin/ipsec"
if not os.path.isfile(ipsec):
ipsec = "/usr/local/sbin/ipsec"
if not os.path.isfile(ipsec):
- print >> sys.stderr, "ipseckey: ipsec command not found (not a libreswan or openswan IPsec servers?)"
+ print("ipseckey: ipsec command not found (not a libreswan or openswan IPsec servers?)", file=sys.stderr)
sys.exit(1)
def show_version():
- print >> sys.stderr, "sshfp version: " + VERSION
+ print("sshfp version: " + VERSION, file=sys.stderr)
def main():
@@ -52,13 +52,13 @@ def main():
sys.exit(0)
if os.geteuid() != 0:
- print >> sys.stderr, "ipseckey: root access is needed to read the IPsec NSS database for the public key"
+ print("ipseckey: root access is needed to read the IPsec NSS database for the public key", file=sys.stderr)
sys.exit(2)
- cmd = [ipsec, "showhostkey", "--ipseckey"]
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- (stdout, stderr) = process.communicate()
- print stdout
+ cmd = [ipsec, "showhostkey", "--ipseckey"]
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
+ (stdout, stderr) = process.communicate()
+ print(stdout)
if __name__ == "__main__":
main()
diff --git a/openpgpkey b/openpgpkey
index e7ca0e6..e423817 100755
--- a/openpgpkey
+++ b/openpgpkey
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
#
# openpgpkey: create OPENPGPKEY DNS record from a key in your keychain.
#
@@ -22,7 +22,7 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-VERSION="2.7"
+VERSION="3.1"
OPENPGPKEY=61
import sys
@@ -45,17 +45,17 @@ def createOPENPGPKEY(email, gpgdisk, keyid, output, debug):
euser = sha256trunc(user)
if output == "rfc" or output == "both":
- print "; keyid: %s" % keyid
- print "%s._openpgpkey.%s. IN OPENPGPKEY %s" % (euser, domain, ekey64)
+ print("; keyid: %s" % keyid)
+ print("%s._openpgpkey.%s. IN OPENPGPKEY %s" % (euser, domain, ekey64))
if output == "generic" or output == "both":
if debug:
- print "Length for generic record is %s" % len(ekey)
- print "; keyid: %s" % keyid
- print "%s._openpgpkey.%s. IN TYPE61 \# %s %s" % (euser, domain, len(ekey), asctohex(ekey))
+ print("Length for generic record is %s" % len(ekey))
+ print("; keyid: %s" % keyid)
+ print("%s._openpgpkey.%s. IN TYPE61 \# %s %s" % (euser, domain, len(ekey), asctohex(ekey)))
def sha256trunc(data):
"""Compute SHA2-256 hash truncated to 28 octets."""
- return hashlib.sha256(data).hexdigest()[:56]
+ return hashlib.sha256(data.encode(encoding="utf-8")).hexdigest()[:56]
def getOPENPGPKEY(email,insecure_ok):
"""This function queries for an OPENPGPKEY DNS Resource Record and compares it with the local gnupg keyring"""
@@ -76,7 +76,7 @@ def getOPENPGPKEY(email,insecure_ok):
# The data is insecure and a secure lookup was requested
sys.exit("Error: query data is not secured by DNSSEC - use --insecure to override")
else:
- print >> sys.stderr, 'Warning: query data was not secured by DNSSEC.'
+ print('Warning: query data was not secured by DNSSEC.', file=sys.stderr)
# If we are here the data was either secure or insecure data is accepted
return result.data.raw
else:
@@ -122,7 +122,7 @@ if __name__ == '__main__':
if os.path.isfile(args.rootanchor):
rootanchor = args.rootanchor
else:
- print >> sys.stdout, 'openpgpkey: %s is not a file. Unable to use it as rootanchor' % args.rootanchor
+ print('openpgpkey: %s is not a file. Unable to use it as rootanchor' % args.rootanchor, file=sys.stdout)
sys.exit(1)
else:
cauldron = ( "/var/lib/unbound/root.anchor", "/var/lib/unbound/root.key", "/etc/unbound/root.key" )
@@ -140,13 +140,13 @@ if __name__ == '__main__':
if os.path.isfile(args.resolvconf):
resolvconf = args.resolvconf
else:
- print >> sys.stdout, 'openpgpkey: %s is not a file. Unable to use it as resolv.conf' % args.resolvconf
+ print('openpgpkey: %s is not a file. Unable to use it as resolv.conf' % args.resolvconf, file=sys.stdout)
sys.exit(1)
ctx.resolvconf(resolvconf)
openpgpkeys = getOPENPGPKEY(args.email, args.insecure)
if len(openpgpkeys) == 0:
- print >> sys.stderr, 'openpgpkey: Received nothing?'
+ print('openpgpkey: Received nothing?', file=sys.stderr)
sys.exit(1)
fdir = tempfile.mkdtemp(".gpg","openpgpkey-","/tmp/")
gpgnet = gnupg.GPG(gnupghome=fdir)
@@ -158,21 +158,21 @@ if __name__ == '__main__':
if args.keyid:
pubkey = gpgnet.export_keys(args.keyid, minimal=True)
if not pubkey:
- print >> sys.stderr, 'openpgpkey: Requested keyid not present in received OpenPGP data'
+ print('openpgpkey: Requested keyid not present in received OpenPGP data', file=sys.stderr)
sys.exit(1)
if args.uid:
pubkey = gpgnet.export_keys(args.uid, minimal=True)
if not pubkey:
- print >> sys.stderr, 'openpgpkey: Requested uid not present in received OpenPGP data'
+ print('openpgpkey: Requested uid not present in received OpenPGP data', file=sys.stderr)
for id in gpgnet.list_keys()[0]['uids']:
- print >> sys.stderr, "# %s"%id
+ print("# %s"%id, file=sys.stderr)
sys.exit(1)
if not args.uid and not args.keyid:
pubkey = gpgnet.export_keys(args.email, minimal=True)
- print >> sys.stderr, 'openpgpkey: Received OpenPGP data does not contain a key with keyid %s'%args.email
- print >> sys.stderr, '(add --uid <uid> to override with any of the below received uids)'
+ print('openpgpkey: Received OpenPGP data does not contain a key with keyid %s'%args.email, file=sys.stderr)
+ print('(add --uid <uid> to override with any of the below received uids)', file=sys.stderr)
for id in gpgnet.list_keys()[0]['uids']:
- print >> sys.stderr, "# %s"%id
+ print("# %s"%id, file=sys.stderr)
sys.exit(1)
pubkey = pubkey.replace("Version:","Comment: %s key obtained from DNS\nVersion:"%args.email)
@@ -180,7 +180,7 @@ if __name__ == '__main__':
pubkey = pubkey.replace("Version:","Comment: NOT VALIDATED BY DNSSEC\nVersion:")
else:
pubkey = pubkey.replace("Version:","Comment: key transfer was protected by DNSSEC\nVersion:")
- print pubkey
+ print(pubkey)
if args.fetch:
sys.exit(0)
@@ -191,11 +191,11 @@ if __name__ == '__main__':
disk_keys = gpgdisk.list_keys()
for pkey in received_keys:
if args.debug:
- print >> sys.stderr, "Received from DNS: Key-ID:%s Fingerprint:%s"%(pkey["keyid"], pkey["fingerprint"])
+ print("Received from DNS: Key-ID:%s Fingerprint:%s"%(pkey["keyid"], pkey["fingerprint"]), file=sys.stderr)
found = False
for dkey in disk_keys:
if args.debug:
- print >> sys.stderr, "Local disk: Key-ID:%s Fingerprint:%s"%(dkey["keyid"], dkey["fingerprint"])
+ print("Local disk: Key-ID:%s Fingerprint:%s"%(dkey["keyid"], dkey["fingerprint"]), file=sys.stderr)
if pkey["keyid"] == dkey["keyid"] and pkey["fingerprint"] == dkey["fingerprint"]:
found = True
if found == False:
@@ -203,8 +203,8 @@ if __name__ == '__main__':
sys.exit("Received key with keyid %s was not found"%pkey["keyid"])
else:
if args.debug:
- print >> sys.stderr, "Received key with keyid %s was found"%pkey["keyid"]
- print "All OPENPGPKEY records matched with content from the local keyring"
+ print("Received key with keyid %s was found"%pkey["keyid"], file=sys.stderr)
+ print("All OPENPGPKEY records matched with content from the local keyring")
shutil.rmtree(fdir)
sys.exit(0)
@@ -225,7 +225,7 @@ if __name__ == '__main__':
for uid in pgpkey["uids"]:
if "<%s>"%args.email in uid:
if args.debug:
- print >> sys.stderr, "Found matching KeyID: %s (%s) for %s"%(pgpkey["keyid"], pgpkey["fingerprint"], uid)
+ print("Found matching KeyID: %s (%s) for %s"%(pgpkey["keyid"], pgpkey["fingerprint"], uid), file=sys.stderr)
ekey = gpgdisk.export_keys(pgpkey["keyid"],minimal=True, secret=False, armor=False)
createOPENPGPKEY(args.email, gpgdisk, pgpkey["keyid"], args.output, args.debug)
found = True
diff --git a/openpgpkey.1 b/openpgpkey.1
index 9be09ee..be206db 100644
--- a/openpgpkey.1
+++ b/openpgpkey.1
@@ -40,7 +40,7 @@ openpgpkey [\fB\-\-create\fR] [\fB\-\-insecure\fR] [\fB\-\-resolv\&.conf /PATH/T
\fIuser@domain\fR
.SH "DESCRIPTION"
.PP
-openpgpkey generates RFC\-TBD OPENPGPKEY DNS records\&. To generate these records for older nameserver implementations that do not yet support the OPENPGPKEY record, specify
+openpgpkey generates RFC\-7929 OPENPGPKEY DNS records\&. To generate these records for older nameserver implementations that do not yet support the OPENPGPKEY record, specify
\fI\-\-output generic\fR
to output the openpgpkey data in Generic Record (RFC\-3597) format\&. Records are generated by taking all keys with the specified email address associated with it from the user\*(Aqs local GnuPG keychain\&.
.PP
diff --git a/openpgpkey.1.xml b/openpgpkey.1.xml
index 6c2c876..f7470fd 100644
--- a/openpgpkey.1.xml
+++ b/openpgpkey.1.xml
@@ -37,7 +37,7 @@
</refsect1>
<refsect1 id='description'><title>DESCRIPTION</title>
-<para>openpgpkey generates RFC-TBD OPENPGPKEY DNS records. To generate these records for older nameserver
+<para>openpgpkey generates RFC-7929 OPENPGPKEY DNS records. To generate these records for older nameserver
implementations that do not yet support the OPENPGPKEY record, specify <emphasis remap='I'>--output generic</emphasis>
to output the openpgpkey data in Generic Record (RFC-3597) format. Records are generated by taking all keys with the specified
email address associated with it from the user's local GnuPG keychain.
diff --git a/sshfp b/sshfp
index 6e72752..2fa34aa 100755
--- a/sshfp
+++ b/sshfp
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
#
# Generate SSHFP DNS records (RFC4255) from knownhosts files or ssh-keyscan
#
@@ -7,6 +7,7 @@
# Copyright 2014 Gerald Turner <gturner@unzane.com>
# Copyright 2015 Jean-Michel Nirgal Vourgere <jmv_deb@nirgal.com>
# Copyright 2015 Dirk Stoecker <github@dstoecker.de>
+# Copyright 2019 Kishan Takoordyal <kishan@cyberstorm.mu>
#
# 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
@@ -22,7 +23,7 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-VERSION = "2.7"
+VERSION = "3.1"
import os
import sys
@@ -39,10 +40,10 @@ try:
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"
+ print("sshfp requires the python-dns package from http://www.pythondns.org/", file=sys.stderr)
+ print("Fedora: yum install python-dns", file=sys.stderr)
+ print("Debian: apt-get install python-dnspython (NOT python-dns!)", file=sys.stderr)
+ print("openSUSE: zypper in python-dnspython", file=sys.stderr)
sys.exit(1)
global all_hosts
@@ -57,7 +58,7 @@ global algo
DEFAULT_KNOWN_HOSTS_FILE = "~/.ssh/known_hosts"
def show_version():
- print >> sys.stderr, "sshfp version: " + VERSION
+ print("sshfp version: " + VERSION, file=sys.stderr)
def create_sshfp(hostname, keytype, keyblob, digesttype):
"""Creates an SSH fingerprint"""
@@ -71,6 +72,8 @@ def create_sshfp(hostname, keytype, keyblob, digesttype):
elif keytype == "ssh-ed25519":
# TBD http://tools.ietf.org/html/draft-moonesamy-sshfp-ed25519-01
keytype = "4"
+ elif keytype == "ssh-xmss":
+ keytype = "5"
else:
return ""
if digesttype == "sha1":
@@ -84,7 +87,7 @@ def create_sshfp(hostname, keytype, keyblob, digesttype):
try:
rawkey = base64.b64decode(keyblob)
except TypeError:
- print >> sys.stderr, "FAILED on hostname "+hostname+" with keyblob "+keyblob
+ print("FAILED on hostname "+hostname+" with keyblob "+keyblob, file=sys.stderr)
return "ERROR"
fp = digest(rawkey).hexdigest().upper()
# check for Reverse entries
@@ -108,10 +111,10 @@ def get_known_host_entry(known_hosts, host):
Uses the ssh-keygen utility."""
cmd = ["ssh-keygen", "-f", known_hosts, "-F", host]
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
(stdout, stderr) = process.communicate()
if not quiet and stderr:
- print >> sys.stderr, stderr
+ print(stderr, file=sys.stderr)
outputl = []
for e in stdout.split("\n"):
if not e.startswith("#"):
@@ -127,7 +130,7 @@ def sshfp_from_file(khfile, wantedHosts):
try:
khfp = open(known_hosts)
except IOError:
- print >> sys.stderr, "ERROR: failed to open file "+ known_hosts
+ print("ERROR: failed to open file "+ known_hosts, file=sys.stderr)
sys.exit(1)
hashed_known_hosts = False
if khfp.readline().startswith("|1|"):
@@ -136,7 +139,7 @@ def sshfp_from_file(khfile, wantedHosts):
fingerprints = []
if hashed_known_hosts and all_hosts:
- print >> sys.stderr, "ERROR: %s is hashed, cannot use with -a" % known_hosts
+ print("ERROR: %s is hashed, cannot use with -a" % known_hosts, file=sys.stderr)
sys.exit(1)
elif hashed_known_hosts: #only looking for some known hosts
for host in wantedHosts:
@@ -145,7 +148,7 @@ def sshfp_from_file(khfile, wantedHosts):
try:
khfp = open(known_hosts)
except IOError:
- print >> sys.stderr, "ERROR: failed to open file "+ known_hosts
+ print("ERROR: failed to open file "+ known_hosts, file=sys.stderr)
sys.exit(1)
data = khfp.read()
khfp.close()
@@ -160,7 +163,7 @@ def check_keytype(keytype, hostname):
if algo in keytype:
return True
if not quiet:
- print >> sys.stderr, "Could only find key type %s for %s" % (keytype, hostname)
+ print("Could only find key type %s for %s" % (keytype, hostname), file=sys.stderr)
return False
def check_hashed(entry, hostnames):
@@ -192,7 +195,7 @@ def process_records(data, hostnames):
(host, keytype, key) = record.split(" ")[:3]
except ValueError:
if not quiet:
- print >> sys.stdout, "Print unable to read record '%s'" % record
+ print("Print unable to read record '%s'" % record, file=sys.stdout)
continue
if "," in host:
host = host.split(",")[0]
@@ -227,25 +230,25 @@ def get_record(domain, qtype):
if qtype == "NS":
return str(rdata.target)
else:
- print >> sys.stderr, "ERROR: error in get_record, unknown type " + qtype
+ print("ERROR: error in get_record, unknown type " + qtype, file=sys.stderr)
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
+ 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"
+ print("ERROR: space in domain '"+domain+"' can't be right, aborted", file=sys.stderr)
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"
+ print("WARNING: no NS record found for domain "+domain+". trying as host record instead", file=sys.stderr)
# better then nothing
return sshfp_from_dns([domain])
hosts = []
@@ -253,8 +256,8 @@ def sshfp_from_axfr(domain, nameserver):
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)
+ except dns.exception.FormError as badDomain:
+ print("AXFR error: %s - No permission or not authorative for %s; aborting" % (nameserver, badDomain), file=sys.stderr)
sys.exit(1)
for (name, ttl, rdata) in axfr.iterate_rdatas('A'):
@@ -273,14 +276,14 @@ def sshfp_from_dns(hosts):
global algos
if "dsa" in algos:
- print >> sys.stderr, "WARNING: openssh has obsoleted dsa/dss keys"
+ print("WARNING: openssh has obsoleted dsa/dss keys", file=sys.stderr)
cmd = ["ssh-keyscan", "-p", str(port), "-T", str(timeout), "-t", ",".join(algos)] + hosts
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
(stdout, stderr) = process.communicate()
stderr = re.sub("#.*\n", "", stderr) # strip scan comments
if not quiet:
- print >> sys.stderr, stderr
+ print(stderr, file=sys.stderr)
khdns = stdout
return process_records(khdns, hosts)
@@ -341,9 +344,9 @@ def main():
action="append",
type="choice",
dest="algo",
- choices=["rsa", "ecdsa", "ed25519", "dsa"],
+ choices=["rsa", "ecdsa", "ed25519", "dsa", "xmss"],
default=[],
- help="key type to fetch (may be specified more than once, default rsa,ecdsa,ed25519,dsa)")
+ help="key type to fetch (may be specified more than once, default rsa,ecdsa,ed25519,dsa,xmss)")
parser.add_option("--digest",
action="append",
type="choice",
@@ -369,7 +372,7 @@ def main():
data = ""
trailing = options.trailing_dot
timeout = options.timeout
- algos = options.algo or ["rsa", "ecdsa", "ed25519"]
+ algos = options.algo or ["rsa", "ecdsa", "ed25519", "xmss"]
digests = options.digest or ["sha256"]
all_hosts = options.all_hosts
port = options.port
@@ -379,16 +382,16 @@ def main():
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"
+ print("WARNING: non-standard port numbers are not designated in SSHFP records", file=sys.stderr)
if not quiet and options.known_hosts and options.scan:
- print >> sys.stderr, "WARNING: Ignoring known hosts option -k , -s was passed"
+ print("WARNING: Ignoring known hosts option -k , -s was passed", file=sys.stderr)
if options.nameserver and not options.scan and not options.all_hosts:
- print >> sys.stderr, "ERROR: Cannot specify -n without -s and -a"
+ print("ERROR: Cannot specify -n without -s and -a", file=sys.stderr)
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"
+ print("WARNING: -a and hosts both passed, ignoring manual host list", file=sys.stderr)
if not args and (not all_hosts):
- print >> sys.stderr, "WARNING: Assuming -a"
+ print("WARNING: Assuming -a", file=sys.stderr)
all_hosts = True
if options.scan and options.all_hosts:
@@ -402,7 +405,7 @@ def main():
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"
+ print("ERROR: You asked me to scan, but didn't give any hosts to scan", file=sys.stderr)
sys.exit(1)
data = sshfp_from_dns(args)
else: # known_hosts
@@ -412,13 +415,13 @@ def main():
try:
fp = open(output, "w")
except IOError:
- print >> sys.stderr, "ERROR: can't open '%s'' for writing" % output
+ print("ERROR: can't open '%s'' for writing" % output, file=sys.stderr)
sys.exit(1)
else:
fp.write(data)
fp.close()
else:
- print data
+ print(data)
if __name__ == "__main__":
main()
diff --git a/sshfp.1 b/sshfp.1
index 84e33ac..5be9e25 100644
--- a/sshfp.1
+++ b/sshfp.1
@@ -44,7 +44,7 @@ sshfp
<digest>] [\fB\-n <nameserver\fR>\fI] <domain1\fR> [\fIdomain2\fR] <\fIhost1\fR> [\fIhost2 \&.\&.\&.\fR] >
.SH "DESCRIPTION"
.PP
-sshfp generates RFC\-4255 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\&.
+sshfp generates RFC\-4255 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 transfers (AXFR), an entire domain can be processed for all its A records\&.
.SH "OPTIONS"
.PP
\fB\-s / \-\-scan\fR <\fIhostname1\fR> [hostname2 \&.\&.\&.]
@@ -59,12 +59,12 @@ Obtain public SSH keys from a known_hosts file\&. Defaults to using ~/\&.ssh/kno
.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\&.
+Scan all hosts in the known_hosts file when used with \-k\&. When used with \-s, it will attempt a 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\&.redhat\&.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\&.
+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\&.redhat\&.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 edited for trailing dots\&.
.RE
.PP
\fB\-o / \-\-output\fR <\fIfilename\fR>
diff --git a/sshfp.1.xml b/sshfp.1.xml
index 1c969fe..8d71017 100644
--- a/sshfp.1.xml
+++ b/sshfp.1.xml
@@ -29,7 +29,7 @@ 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.</para>
+the hostname to be scanned. If the nameserver of the domain allows zone transfers (AXFR), an entire domain can be processed for all its A records.</para>
</refsect1>
<refsect1 id='options'><title>OPTIONS</title>
@@ -49,7 +49,7 @@ the hostname to be scanned. If the nameserver of the domain allows zone tranfers
<varlistentry>
<term><option>-a / --all</option></term>
<listitem>
-<para>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.</para>
+<para>Scan all hosts in the known_hosts file when used with -k. When used with -s, it will attempt a zone transfer (AXFR) to obtain all A records in the domain specified.</para>
</listitem>
</varlistentry>
<varlistentry>
@@ -63,7 +63,7 @@ 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.</para>
+sshfp can also just be manually edited for trailing dots.</para>
</listitem>
</varlistentry>
<varlistentry>
diff --git a/tlsa b/tlsa
index 65c4d60..ae925ea 100755
--- a/tlsa
+++ b/tlsa
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
# tlsa - A tool to create DANE/TLSA records. (called 'swede' before)
#
@@ -7,7 +7,7 @@
#
# Copyright 2012 Pieter Lexis <pieter.lexis@os3.nl>
# Copyright 2014 Paul Wouters <pwouters@redhat.com>
-# Copyright 2015 Dirk Stoecker <hashslinger@dstoecker.de>
+# Copyright 2015-2019 Dirk Stoecker <hashslinger@dstoecker.de>
#
# 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
@@ -23,7 +23,7 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-VERSION="2.7"
+VERSION="3.1"
import sys
import os
@@ -32,9 +32,9 @@ import unbound
import subprocess
import re
from M2Crypto import X509, SSL, BIO, m2
-from binascii import a2b_hex, b2a_hex
+from binascii import b2a_hex
from hashlib import sha256, sha512
-from ipaddr import IPv4Address, IPv6Address
+from ipaddress import IPv4Address, IPv6Address
ROOTKEY="none"
cauldron = ( "/var/lib/unbound/root.anchor", "/var/lib/unbound/root.key", "/etc/unbound/root.key" )
@@ -71,17 +71,17 @@ def genTLSA(hostname, protocol, port, certificate, output='rfc', usage=1, select
return record.getRecord(generic=True)
return record.getRecord()
-def genRecords(hostname, protocol, port, chain, output='rfc', usage=1, selector=0, mtype=1):
+def genRecords(hostname, address, protocol, port, chain, output='rfc', usage=1, selector=0, mtype=1):
cert = None
chained = checkChainLink(chain)[0]
if not chained:
- print "WARN: Certificates don't chain"
+ print("WARN: Certificates don't chain")
certsleft = len(chain)
for chaincert in chain:
certsleft -= 1
if usage == 1 or usage == 3:
# The first cert is the end-entity cert
- print 'Got a certificate with Subject: %s' % chaincert.get_subject()
+ print('Got a certificate for %s with Subject: %s' % (address, chaincert.get_subject()))
cert = chaincert
break
else:
@@ -90,7 +90,7 @@ def genRecords(hostname, protocol, port, chain, output='rfc', usage=1, selector=
sys.stdout.write('Got a certificate with the following Subject:\n\t%s\nUse this as certificate to match? [y/N] ' % chaincert.get_subject())
input_ok = False
while not input_ok:
- user_input = raw_input()
+ user_input = input()
if user_input in ['','n','N']:
input_ok=True
elif user_input in ['y', 'Y']:
@@ -106,37 +106,37 @@ def genRecords(hostname, protocol, port, chain, output='rfc', usage=1, selector=
if cert: # Print the requested records based on the retrieved certificates
if output == 'both':
- print genTLSA(hostname, protocol, port, cert, 'draft', usage, selector, mtype)
- print genTLSA(hostname, protocol, port, cert, 'rfc', usage, selector, mtype)
+ print(genTLSA(hostname, protocol, port, cert, 'draft', usage, selector, mtype))
+ print(genTLSA(hostname, protocol, port, cert, 'rfc', usage, selector, mtype))
else:
- print genTLSA(hostname, protocol, port, cert, output, usage, selector, mtype)
+ print(genTLSA(hostname, protocol, port, cert, output, usage, selector, mtype))
# Clear the cert from memory (to stop M2Crypto from segfaulting)
cert=None
def getA(hostname, secure=True):
"""Gets a list of A records for hostname, returns a list of ARecords"""
- records = ""
+ records = []
try:
records = getRecords(hostname, rrtype='A', secure=secure)
- except InsecureLookupException, e:
- print str(e)
- except DNSLookupError, e:
- print 'Unable to resolve %s: %s' % (hostname, str(e))
+ except InsecureLookupException as e:
+ print(str(e))
+ except DNSLookupError as e:
+ print('Unable to resolve %s: %s' % (hostname, str(e)))
ret = []
for record in records:
ret.append(ARecord(hostname, str(IPv4Address(int(b2a_hex(record),16)))))
return ret
def getAAAA(hostname, secure=True):
- """Gets a list of A records for hostname, returns a list of AAAARecords"""
- records = ""
+ """Gets a list of AAAA records for hostname, returns a list of AAAARecords"""
+ records = []
try:
records = getRecords(hostname, rrtype='AAAA', secure=secure)
- except InsecureLookupException, e:
- print str(e)
- except DNSLookupError, e:
- print 'Unable to resolve %s: %s' % (hostname, str(e))
+ except InsecureLookupException as e:
+ print(str(e))
+ except DNSLookupError as e:
+ print('Unable to resolve %s: %s' % (hostname, str(e)))
ret = []
for record in records:
ret.append(AAAARecord(hostname, str(IPv6Address(int(b2a_hex(record),16)))))
@@ -210,13 +210,13 @@ def getRecords(hostname, rrtype='A', secure=True, noresolver=False):
if secure:
if not noresolver and resolvconf:
retval = getRecords(hostname, rrtype, secure, True)
- print >> sys.stderr, 'Warning: initial query using resolver config file was not secure (try option --resolvconf="").'
+ print('Warning: initial query using resolver config file was not secure (try option --resolvconf="").', file=sys.stderr)
resolvconf = None
return retval
# The data is insecure and a secure lookup was requested
raise InsecureLookupException('Error: Answer was not DNSSEC-secure')
else:
- print >> sys.stderr, 'Warning: query data is not secure.'
+ print('Warning: query data is not secure.', file=sys.stderr)
# If we are here the data was either secure or insecure data is accepted
return result.data.raw
else:
@@ -230,21 +230,21 @@ def sslStartTLSConnect(connection, addr, starttls=None):
# primitive method, no error checks yet
if starttls == "smtp":
data = connection.socket.recv(500)
- connection.socket.send("EHLO M2Crypto\r\n")
+ connection.socket.send("EHLO M2Crypto\r\n".encode('ascii'))
data = connection.socket.recv(500)
- connection.socket.send("STARTTLS\r\n")
+ connection.socket.send("STARTTLS\r\n".encode('ascii'))
data = connection.socket.recv(500)
elif starttls == "imap":
data = connection.socket.recv(500)
- connection.socket.send(". STARTTLS\r\n")
+ connection.socket.send(". STARTTLS\r\n".encode('ascii'))
data = connection.socket.recv(500)
elif starttls == "ftp":
data = connection.socket.recv(500)
- connection.socket.send("AUTH TLS\r\n")
+ connection.socket.send("AUTH TLS\r\n".encode('ascii'))
data = connection.socket.recv(500)
elif starttls == "pop3":
data = connection.socket.recv(500)
- connection.socket.send("STLS\r\n")
+ connection.socket.send("STLS\r\n".encode('ascii'))
data = connection.socket.recv(500)
connection.setup_ssl()
connection.set_connect_state()
@@ -261,7 +261,7 @@ def getHash(certificate, mtype):
"""
certificate = certificate.as_der()
if mtype == 0:
- return b2a_hex(certificate)
+ return certificate.hex()
elif mtype == 1:
return sha256(certificate).hexdigest()
elif mtype == 2:
@@ -285,19 +285,19 @@ def getTLSA(hostname, port=443, protocol='tcp', secure=True):
records = getRecords('*._%s.%s' % (protocol.lower(), hostname), rrtype=52, secure=secure)
else:
records = getRecords('_%s._%s.%s' % (port, protocol.lower(), hostname), rrtype=52, secure=secure)
- except InsecureLookupException, e:
- print str(e)
+ except InsecureLookupException as e:
+ print(str(e))
sys.exit(1)
- except DNSLookupError, e:
- print 'Unable to resolve %s: %s' % (hostname, str(e))
+ except DNSLookupError as e:
+ print('Unable to resolve %s: %s' % (hostname, str(e)))
sys.exit(1)
ret = []
for record in records:
hexdata = b2a_hex(record)
if port == '*':
- ret.append(TLSARecord('*._%s.%s' % (protocol.lower(), hostname), int(hexdata[0:2],16), int(hexdata[2:4],16), int(hexdata[4:6],16), hexdata[6:]))
+ ret.append(TLSARecord('*._%s.%s' % (protocol.lower(), hostname), int(hexdata[0:2],16), int(hexdata[2:4],16), int(hexdata[4:6],16), hexdata[6:].decode('ascii')))
else:
- ret.append(TLSARecord('_%s._%s.%s' % (port, protocol.lower(), hostname), int(hexdata[0:2],16), int(hexdata[2:4],16), int(hexdata[4:6],16), hexdata[6:]))
+ ret.append(TLSARecord('_%s._%s.%s' % (port, protocol.lower(), hostname), int(hexdata[0:2],16), int(hexdata[2:4],16), int(hexdata[4:6],16), hexdata[6:].decode('ascii')))
return ret
def verifyCertMatch(record, cert):
@@ -351,13 +351,13 @@ def verifyCertNameWithHostName(cert, hostname, with_msg=False):
return True
if with_msg:
- print 'WARNING: Name on the certificate (Subject: %s, SubjectAltName: %s) doesn\'t match requested hostname (%s).' % (str(cert.get_subject()), cert.get_ext('subjectAltName').get_value(), hostname)
+ print('WARNING: Name on the certificate (Subject: %s, SubjectAltName: %s) doesn\'t match requested hostname (%s).' % (str(cert.get_subject()), cert.get_ext('subjectAltName').get_value(), hostname))
return False
def checkChainLink(chain, record=None):
previous_issuer = None
chained = True
- matched = False
+ matched = None
for cert in chain:
if previous_issuer:
if not str(previous_issuer) == str(cert.get_subject()): # The chain cannot be valid
@@ -365,27 +365,35 @@ def checkChainLink(chain, record=None):
break
previous_issuer = cert.get_issuer()
if record and verifyCertMatch(record, cert):
- matched = True
+ matched = cert
return chained, matched
def getLocalChain(filename, debug):
"""Returns list of M2Crypto.X509.X509 objects and verification result"""
chain = []
bio = BIO.openfile(filename)
+ Err = None
while True:
- cptr = m2.x509_read_pem(bio._ptr())
+ try:
+ cptr = m2.x509_read_pem(bio._ptr())
+ except Exception as e:
+ Err = e
+ break;
if not cptr:
break
chain.append(X509.X509(cptr, _pyfree=1))
if not chain:
- raise X509Error(Err.get_error())
+ if Err:
+ raise
+ else:
+ raise Exception("Could not load %s" % filename)
# FIXME - is this possible using the library (without a call to openssl tool)?
cmd = ["openssl", "verify", filename]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = process.communicate()
if debug:
- print >> sys.stderr, stderr
- if stdout.endswith("OK\n"):
+ print(stderr, file=sys.stderr)
+ if stdout.endswith(b"OK\n"):
verify_result = 0
else:
verify_result = "%s%s" % (stdout, stderr)
@@ -405,25 +413,25 @@ def verifyChain(chain, verify_result, pre_exit, local, source):
if not verifyCertNameWithHostName(cert=chain[0], hostname=str(args.host), with_msg=True):
# The name on the cert doesn't match the hostname... we don't verify the TLSA record
- print 'Caution: %s does not match hostname (%s)' % (text_cert, source)
+ print('Caution: %s does not match hostname (%s)' % (text_cert, source))
ut = usageText(record.usage)
if record.usage == 1: # End-host cert
chained, matched = checkChainLink((chain[0],), record)
if not chained:
- print "WARN: Certificates don't chain"
+ print("WARN: Certificates don't chain")
if matched:
if verify_result == 0: # The cert chains to a valid CA cert according to the system-certificates
- print 'SUCCESS (%s): %s matches the one mentioned in the TLSA record and chains to a valid CA certificate (%s)' % (ut, text_cert, source)
+ print('SUCCESS (%s): %s matches the one mentioned in the TLSA record and chains to a valid CA certificate (%s)' % (ut, text_cert, source))
else:
if local:
reason = verify_result
else:
reason = getVerificationErrorReason(verify_result)
- print 'FAIL (%s): %s matches the one mentioned in the TLSA record but the following error was raised during PKIX validation (%s): %s' % (ut, text_cert, source, reason)
+ print('FAIL (%s): %s matches the one mentioned in the TLSA record but the following error was raised during PKIX validation (%s): %s' % (ut, text_cert, source, reason))
if pre_exit == 0: pre_exit = 2
- if args.debug: print 'The matched certificate has Subject: %s' % cert.get_subject()
+ if args.debug: print('The matched certificate has Subject: %s' % cert.get_subject())
else:
- print 'FAIL: %s does not match the TLSA record (%s)' % (text_cert, source)
+ print('FAIL: %s does not match the TLSA record (%s)' % (text_cert, source))
if pre_exit == 0: pre_exit = 2
elif record.usage == 0: # CA constraint
@@ -435,24 +443,24 @@ def verifyChain(chain, verify_result, pre_exit, local, source):
# Remove the first (= End-Entity cert) from the chain
chained, matched = checkChainLink(chain[1:], record)
if not chained:
- print "WARN: Certificates don't chain"
+ print("WARN: Certificates don't chain")
if matched:
if cert.check_ca():
if verify_result == 0:
- print 'SUCCESS (%s): A %s matches the one mentioned in the TLSA record and is a CA certificate (%s)' % (ut, text_chain, source)
+ print('SUCCESS (%s): A %s matches the one mentioned in the TLSA record and is a CA certificate (%s)' % (ut, text_chain, source))
else:
if local:
reason = verify_result
else:
reason = getVerificationErrorReason(verify_result)
- print 'FAIL (%s): A %s matches the one mentioned in the TLSA record and is a CA certificate, but the following error was raised during PKIX validation (%s): %s' % (ut, text_chain, source, reason)
+ print('FAIL (%s): A %s matches the one mentioned in the TLSA record and is a CA certificate, but the following error was raised during PKIX validation (%s): %s' % (ut, text_chain, source, reason))
if pre_exit == 0: pre_exit = 2
else:
- print 'FAIL (%s): A %s matches the one mentioned in the TLSA record but is not a CA certificate (%s)' % (ut, text_chain, source)
+ print('FAIL (%s): A %s matches the one mentioned in the TLSA record but is not a CA certificate (%s)' % (ut, text_chain, source))
if pre_exit == 0: pre_exit = 2
- if args.debug: print 'The matched certificate has Subject: %s' % cert.get_subject()
+ if args.debug: print('The matched certificate has Subject: %s' % cert.get_subject())
else:
- print 'FAIL (%s): No %s matches the TLSA record (%s)' % (ut, text_chain, source)
+ print('FAIL (%s): No %s matches the TLSA record (%s)' % (ut, text_chain, source))
if pre_exit == 0: pre_exit = 2
elif record.usage == 2: # Usage 2, use the cert in the record as trust anchor
@@ -462,24 +470,24 @@ def verifyChain(chain, verify_result, pre_exit, local, source):
chained, matched = checkChainLink(chain, record)
if matched:
if not chained:
- print "WARN: Certificates don't chain"
- print 'SUCCESS (%s): A %s (including the end-entity certificate) matches the TLSA record (%s)' % (ut, text_chain, source)
- if args.debug: print 'The matched certificate has Subject: %s' % cert.get_subject()
+ print("WARN: Certificates don't chain")
+ print('SUCCESS (%s): A %s (including the end-entity certificate) matches the TLSA record (%s)' % (ut, text_chain, source))
+ if args.debug: print('The matched certificate has Subject: %s' % matched.get_subject())
else:
if not chained:
- print "FAIL: Certificates don't chain"
- print 'FAIL (%s): No %s (including the end-entity certificate) matches the TLSA record (%s)' % (ut, text_chain, source)
+ print("FAIL: Certificates don't chain")
+ print('FAIL (%s): No %s (including the end-entity certificate) matches the TLSA record (%s)' % (ut, text_chain, source))
if pre_exit == 0: pre_exit = 2
elif record.usage == 3: # EE cert MUST match
chained, matched = checkChainLink((chain[0],), record)
if not chained:
- print "WARN: Certificates don't chain"
+ print("WARN: Certificates don't chain")
if matched:
- print 'SUCCESS (%s): %s matches the TLSA record (%s)' % (ut, text_cert, source)
- if args.debug: print 'The matched certificate has Subject: %s' % chain[0].get_subject()
+ print('SUCCESS (%s): %s matches the TLSA record (%s)' % (ut, text_cert, source))
+ if args.debug: print('The matched certificate has Subject: %s' % chain[0].get_subject())
else:
- print 'FAIL (%s): %s does not match the TLSA record (%s)' % (ut, text_cert, source)
+ print('FAIL (%s): %s does not match the TLSA record (%s)' % (ut, text_cert, source))
if pre_exit == 0: pre_exit = 2
return pre_exit
@@ -498,7 +506,7 @@ class TLSARecord:
self.usage = int(usage)
self.selector = int(selector)
self.mtype = int(mtype)
- self.cert = str(cert)
+ self.cert = cert
except:
raise Exception('Invalid value passed, unable to create a TLSARecord')
@@ -654,20 +662,20 @@ if __name__ == '__main__':
if os.path.isfile(args.rootkey):
ROOTKEY = args.rootkey
else:
- print ("ignored specified non-existing rootkey file %s"%args.rootkey)
+ print("ignored specified non-existing rootkey file %s"%args.rootkey)
# check whether it's ASCII or needs to be converted into Punnycode
try:
- args.host.decode('ascii')
- except UnicodeDecodeError:
- args.host = unicode(args.host, 'utf-8').encode("idna")
+ args.host.encode('ascii')
+ except UnicodeEncodeError:
+ args.host = str(args.host.encode('idna'), 'utf-8')
if args.host[-1] != '.':
args.host += '.'
snihost = args.host[0:-1]
if not args.starttls:
- if args.port == "25":
+ if args.port == "25" or args.port == "587":
args.starttls = "smtp"
elif args.port == "143":
args.starttls = "imap"
@@ -683,7 +691,7 @@ if __name__ == '__main__':
if os.path.isfile(args.resolvconf):
resolvconf = args.resolvconf
else:
- print >> sys.stdout, '%s is not a file. Unable to use it as resolv.conf' % args.resolvconf
+ print('%s is not a file. Unable to use it as resolv.conf' % args.resolvconf, file=sys.stdout)
sys.exit(1)
else:
resolvconf = None
@@ -700,20 +708,20 @@ if __name__ == '__main__':
pre_exit = 0
# First, check if the first three fields have correct values.
if args.debug:
- print 'Received the following record for name %s:' % record.name
- print '\tUsage:\t\t\t\t%d (%s)' % (record.usage, {0:'CA Constraint [PKIX-CA]', 1:'End-Entity Constraint + chain to CA [PKIX-EE]', 2:'Trust Anchor [DANE-TA]', 3:'End-Entity [DANE-EE]'}.get(record.usage, 'INVALID'))
- print '\tSelector:\t\t\t%d (%s)' % (record.selector, {0:'Certificate [Cert]', 1:'SubjectPublicKeyInfo [SPKI]'}.get(record.selector, 'INVALID'))
- print '\tMatching Type:\t\t\t%d (%s)' % (record.mtype, {0:'Full Certificate', 1:'SHA-256', 2:'SHA-512'}.get(record.mtype, 'INVALID'))
- print '\tCertificate for Association:\t%s' % record.cert
+ print('Received the following record for name %s:' % record.name)
+ print('\tUsage:\t\t\t\t%d (%s)' % (record.usage, {0:'CA Constraint [PKIX-CA]', 1:'End-Entity Constraint + chain to CA [PKIX-EE]', 2:'Trust Anchor [DANE-TA]', 3:'End-Entity [DANE-EE]'}.get(record.usage, 'INVALID')))
+ print('\tSelector:\t\t\t%d (%s)' % (record.selector, {0:'Certificate [Cert]', 1:'SubjectPublicKeyInfo [SPKI]'}.get(record.selector, 'INVALID')))
+ print('\tMatching Type:\t\t\t%d (%s)' % (record.mtype, {0:'Full Certificate', 1:'SHA-256', 2:'SHA-512'}.get(record.mtype, 'INVALID')))
+ print('\tCertificate for Association:\t%s' % record.cert)
try:
record.isValid(raiseException=True)
- except RecordValidityException, e:
- print >> sys.stderr, 'Error: %s' % str(e)
+ except RecordValidityException as e:
+ print('Error: %s' % str(e), file=sys.stderr)
continue
else:
if args.debug:
- print 'This record is valid (well-formed).'
+ print('This record is valid (well-formed).')
if args.only_rr:
# Go to the next record
@@ -721,11 +729,11 @@ if __name__ == '__main__':
# When we are here, The user also wants to verify the certificates with the record
if args.protocol != 'tcp':
- print >> sys.stderr, 'Only SSL over TCP is supported (sorry)'
+ print('Only SSL over TCP is supported (sorry)', file=sys.stderr)
sys.exit(0)
if args.debug:
- print 'Attempting to verify the record with the TLS service...'
+ print('Attempting to verify the record with the TLS service...')
if not args.ipv4 and not args.ipv6:
addresses = getA(args.host, secure=secure) + getAAAA(args.host, secure=secure)
@@ -738,12 +746,12 @@ if __name__ == '__main__':
try:
chain, verify_result = getLocalChain(args.certificate, args.debug)
pre_exit = verifyChain(chain, verify_result, pre_exit, True, args.certificate)
- except Exception, e:
- print 'Could not verify local certificate: %s.' % e
+ except Exception as e:
+ print('Could not verify local certificate: %s.' % e)
sys.exit(0)
for address in addresses:
if args.debug:
- print 'Got the following IP: %s' % str(address)
+ print('Got the following IP: %s' % str(address))
# We do the certificate handling here, as M2Crypto keeps segfaulting when we do it in a method
ctx = SSL.Context()
if os.path.isfile(args.ca_cert):
@@ -751,7 +759,7 @@ if __name__ == '__main__':
elif os.path.exists(args.ca_cert):
if ctx.load_verify_locations(capath=args.ca_cert) != 1: raise Exception('No CA certs')
else:
- print >> sys.stderr, '%s is neither a file nor a directory, unable to continue' % args.ca_cert
+ print('%s is neither a file nor a directory, unable to continue' % args.ca_cert, file=sys.stderr)
sys.exit(1)
# Don't error when the verification fails in the SSL handshake
ctx.set_verify(SSL.verify_none, depth=9)
@@ -764,11 +772,11 @@ if __name__ == '__main__':
try:
connection.set_tlsext_host_name(snihost)
if(args.debug):
- print 'Did set servername %s' % snihost
+ print('Did set servername %s' % snihost)
except AttributeError:
- print 'Could not set SNI (old M2Crypto version?)'
+ print('Could not set SNI (old M2Crypto version?)')
except:
- print 'Could not set SNI'
+ print('Could not set SNI')
try:
if args.starttls:
sslStartTLSConnect(connection, (str(address), int(args.port)), args.starttls)
@@ -778,11 +786,11 @@ if __name__ == '__main__':
#except TypeError:
# print 'Cannot connect to %s (old M2Crypto version not supporting start script?)' % address
# continue
- except SSL.Checker.WrongHost, e:
+ except SSL.Checker.WrongHost as e:
# The name on the remote cert doesn't match the hostname because we connect on IP, not hostname (as we want secure lookup)
pass
- except socket.error, e:
- print 'Cannot connect to %s: %s' % (address, str(e))
+ except socket.error as e:
+ print('Cannot connect to %s: %s' % (address, str(e)))
continue
chain = connection.get_peer_cert_chain()
verify_result = connection.get_verify_result()
@@ -803,16 +811,16 @@ if __name__ == '__main__':
cert = None
if not args.certificate:
if args.protocol != 'tcp':
- print >> sys.stderr, 'Only SSL over TCP is supported (sorry)'
+ print('Only SSL over TCP is supported (sorry)', file=sys.stderr)
sys.exit(1)
if args.debug:
- print 'No certificate specified on the commandline, attempting to retrieve it from the server %s' % (args.host)
+ print('No certificate specified on the commandline, attempting to retrieve it from the server %s' % (args.host))
connection_port = args.port
if args.port == '*':
sys.stdout.write('The port specified on the commandline is *, please specify the port of the TLS service on %s (443): ' % args.host)
input_ok = False
while not input_ok:
- user_input = raw_input()
+ user_input = input()
if user_input == '':
connection_port = 443
break
@@ -833,13 +841,13 @@ if __name__ == '__main__':
else:
addresses = getAAAA(args.host, secure=secure)
- except InsecureLookupException, e:
- print >> sys.stderr, str(e)
+ except InsecureLookupException as e:
+ print(str(e), file=sys.stderr)
sys.exit(1)
for address in addresses:
if args.debug:
- print 'Attempting to get certificate from %s' % str(address)
+ print('Attempting to get certificate from %s' % str(address))
# We do the certificate handling here, as M2Crypto keeps segfaulting when try to do stuff with the cert if we don't
ctx = SSL.Context()
ctx.set_verify(SSL.verify_none, depth=9)
@@ -852,11 +860,11 @@ if __name__ == '__main__':
try:
connection.set_tlsext_host_name(snihost)
if(args.debug):
- print 'Did set servername %s' % snihost
+ print('Did set servername %s' % snihost)
except AttributeError:
- print 'Could not set SNI (old M2Crypto version?)'
+ print('Could not set SNI (old M2Crypto version?)')
except:
- print 'Could not set SNI'
+ print('Could not set SNI')
try:
if args.starttls:
sslStartTLSConnect(connection, (str(address), int(connection_port)), args.starttls)
@@ -868,12 +876,12 @@ if __name__ == '__main__':
# continue
except SSL.Checker.WrongHost:
pass
- except socket.error, e:
- print 'Cannot connect to %s: %s' % (address, str(e))
+ except socket.error as e:
+ print('Cannot connect to %s: %s' % (address, str(e)))
continue
chain = connection.get_peer_cert_chain()
- genRecords(args.host, args.protocol, args.port, chain, args.output, args.usage, args.selector, args.mtype)
+ genRecords(args.host, address, args.protocol, args.port, chain, args.output, args.usage, args.selector, args.mtype)
# Cleanup the connection and context
connection.clear()
connection.close()
@@ -882,8 +890,8 @@ if __name__ == '__main__':
else: # Pass the path to the certificate to the genTLSA function
try:
chain, verify_result = getLocalChain(args.certificate, args.debug)
- except Exception, e:
- print 'Could not verify local certificate: %s.' % e
- if args.usage == 0 or args.usage == 1 and not verify_result:
- print 'Following error was raised during PKIX validation: %s' % verify_result
- genRecords(args.host, args.protocol, args.port, chain, args.output, args.usage, args.selector, args.mtype)
+ if args.usage == 0 or args.usage == 1 and not verify_result:
+ print('Following error was raised during PKIX validation: %s' % verify_result)
+ genRecords(args.host, args.host, args.protocol, args.port, chain, args.output, args.usage, args.selector, args.mtype)
+ except Exception as e:
+ print('Could not verify local certificate: %s.' % e)
diff --git a/tlsa.1 b/tlsa.1
index f25d910..7310772 100644
--- a/tlsa.1
+++ b/tlsa.1
@@ -31,7 +31,7 @@
tlsa \- Create and verify RFC\-6698 TLSA DNS records
.SH "SYNTAX"
.PP
-tlsa [\fB\-h\fR] [\fB\-\-verify\fR] [\fB\-create\fR] [\fB\-\-version\fR] [\fB\-4\fR] [\fB\-6\fR\fB\-\-insecure\fR] [\fB\-\-resolv\&.conf /PATH/TO/RESOLV\&.CONF\fR] [\fB\-\-port PORT\fR] [\fB\-\-starttls {auto,smtp,imap,pop3,ftp}\fR] [\fB\-\-protocol {tcp,udp,sctp}\fR] [\fB\-\-ponly\-rr\fR] [\fB\-\-rootkey /PATH/TO/ROOT\&.KEY\fR] [\fB\-\-ca\-cert /PATH/TO/CERTSTORE\fR] [\fB\-\-debug\fR] [\fB\-\-quiet\fR] [\fB\-\-certificate CERTIFICATE\fR] [\fB\-\-output {rfc,generic,both}\fR] [\fB\-\-usage {0,1,2,3}\fR] [\fB\-\-selector {0,1}\fR] [\fB\-mtype {0,1,2}\fR]
+tlsa [\fB\-h\fR] [\fB\-\-verify\fR] [\fB\-create\fR] [\fB\-\-version\fR] [\fB\-4\fR] [\fB\-6\fR] [\fB\-\-insecure\fR] [\fB\-\-resolv\&.conf /PATH/TO/RESOLV\&.CONF\fR] [\fB\-\-port PORT\fR] [\fB\-\-starttls {auto,smtp,imap,pop3,ftp}\fR] [\fB\-\-protocol {tcp,udp,sctp}\fR] [\fB\-\-only\-rr\fR] [\fB\-\-rootkey /PATH/TO/ROOT\&.KEY\fR] [\fB\-\-ca\-cert /PATH/TO/CERTSTORE\fR] [\fB\-\-debug\fR] [\fB\-\-quiet\fR] [\fB\-\-certificate CERTIFICATE\fR] [\fB\-\-output {rfc,generic,both}\fR] [\fB\-\-usage {0,1,2,3}\fR] [\fB\-\-selector {0,1}\fR] [\fB\-mtype {0,1,2}\fR]
\fIhostname\fR
.SH "DESCRIPTION"
.PP
diff --git a/tlsa.1.xml b/tlsa.1.xml
index bfce0ec..e0bb24a 100644
--- a/tlsa.1.xml
+++ b/tlsa.1.xml
@@ -18,10 +18,10 @@
<refsect1 id='syntax'><title>SYNTAX</title>
<para>tlsa [<option>-h</option>] [<option>--verify</option>] [<option>-create</option>] [<option>--version</option>]
- [<option>-4</option>] [<option>-6</option><option>--insecure</option>]
+ [<option>-4</option>] [<option>-6</option>] [<option>--insecure</option>]
[<option>--resolv.conf /PATH/TO/RESOLV.CONF</option>]
[<option>--port PORT</option>] [<option>--starttls {auto,smtp,imap,pop3,ftp}</option>]
- [<option>--protocol {tcp,udp,sctp}</option>] [<option>--ponly-rr</option>]
+ [<option>--protocol {tcp,udp,sctp}</option>] [<option>--only-rr</option>]
[<option>--rootkey /PATH/TO/ROOT.KEY</option>]
[<option>--ca-cert /PATH/TO/CERTSTORE</option>]
[<option>--debug</option>] [<option>--quiet</option>] [<option>--certificate CERTIFICATE</option>]