Control: tags -1 patch

Please note that I am going to upload a new upload version to DELAYED/10
in order to fix this. Please find the debdiff with the changes attached.
diff -Nru hash-slinger-3.1/CHANGES hash-slinger-3.5/CHANGES
--- hash-slinger-3.1/CHANGES    2021-01-14 11:10:30.000000000 +0100
+++ hash-slinger-3.5/CHANGES    2026-01-26 17:44:59.000000000 +0100
@@ -1,4 +1,25 @@
-v3.1 (January 14, 2020)
+v3.5 (January, 26, 2026)
+- Replace the M2Crypto library with the Python cryptography library and 
standard ssl module for TLS/SSL handling [Bastian Germann]
+- Use cryptography.x509 for X.509 certificate parsing and manipulation 
[Bastian Germann]
+- Use standard ssl module for TLS connections instead of M2Crypto.SSL [Bastian 
Germann]
+- Replace sslStartTLSConnect() with do_starttls_handshake() that works with 
plain sockets before SSL wrapping [Bastian Germann]
+- Update getHash() to accept use_pubkey parameter instead of accepting both 
certificate and public key objects [Bastian Germann]
+
+v3.4 (April, 14, 2025)
+- minor cleanup
+
+v3.3 (June, 6, 2023)
+- tlsa: Add /usr/share/dns/root.key to cauldron [Vijay Sarvepalli]
+- tlsa: Give warning on TLSA create routine for certificate name mismatch to 
hostname provided [Vijay Sarvepalli]
+- openpgp: Fix crash when exporting generic format [Dirk Stöcker]
+
+v3.2 (May, 4, 2022)
+- openpgpkey: fix fetch option [Dirk Stöcker]
+- sshfp: fix UTF-8 encoding issue [Dirk Stöcker]
+- sshfp: fix handling when xmss is not supported [Dirk Stöcker]
+- sshfp: don't warn for default known hosts file and option scan [Dirk Stöcker]
+
+v3.1 (January 14, 2021)
 - 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]
diff -Nru hash-slinger-3.1/debian/changelog hash-slinger-3.5/debian/changelog
--- hash-slinger-3.1/debian/changelog   2023-10-05 16:37:58.000000000 +0200
+++ hash-slinger-3.5/debian/changelog   2026-01-26 20:53:48.000000000 +0100
@@ -1,3 +1,11 @@
+hash-slinger (3.5-0.1) unstable; urgency=medium
+
+  * Non-maintainer upload
+  * Import new upstream version (Closes: #1126463, #1118256)
+  * d/copyright: correct Source
+
+ -- Bastian Germann <[email protected]>  Mon, 26 Jan 2026 20:53:48 +0100
+
 hash-slinger (3.1-1.2) unstable; urgency=medium
 
   * Non-maintainer upload.
diff -Nru hash-slinger-3.1/debian/control hash-slinger-3.5/debian/control
--- hash-slinger-3.1/debian/control     2022-02-10 07:03:46.000000000 +0100
+++ hash-slinger-3.5/debian/control     2026-01-26 20:53:48.000000000 +0100
@@ -19,7 +19,7 @@
          openssh-client,
          python3-dnspython,
          python3-gnupg (>= 0.3.7),
-         python3-m2crypto (>= 0.24.0),
+         python3-cryptography,
          python3-unbound,
          ${misc:Depends},
          ${python3:Depends},
diff -Nru hash-slinger-3.1/debian/copyright hash-slinger-3.5/debian/copyright
--- hash-slinger-3.1/debian/copyright   2021-02-14 17:40:02.000000000 +0100
+++ hash-slinger-3.5/debian/copyright   2026-01-26 20:53:48.000000000 +0100
@@ -1,9 +1,19 @@
 Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 Upstream-Name: hash-slinger
-Source: http://people.redhat.com/pwouters/hash-slinger/
+Source: https://github.com/letoams/hash-slinger
 
 Files: *
-Copyright: 2013, 2014 Paul Wouters <[email protected]>
+Copyright: 2013-2022 Paul Wouters <[email protected]>
+License: GPL-2+
+
+Files: sshfp
+Copyright:
+ Copyright 2006-2010 by Xelerance http://www.xelerance.com/ (Paul Wouters)
+ Copyright 2012 Paul Wouters <[email protected]>
+ Copyright 2014 Gerald Turner <[email protected]>
+ Copyright 2015 Jean-Michel Nirgal Vourgere <[email protected]>
+ Copyright 2015 Dirk Stoecker <[email protected]>
+ Copyright 2019 Kishan Takoordyal <[email protected]>
 License: GPL-2+
 
 Files: tlsa
diff -Nru 
hash-slinger-3.1/debian/patches/0001-Debian-default-root.key-resides-in-usr-share-dns-roo.patch
 
hash-slinger-3.5/debian/patches/0001-Debian-default-root.key-resides-in-usr-share-dns-roo.patch
--- 
hash-slinger-3.1/debian/patches/0001-Debian-default-root.key-resides-in-usr-share-dns-roo.patch
     2021-02-14 17:40:02.000000000 +0100
+++ 
hash-slinger-3.5/debian/patches/0001-Debian-default-root.key-resides-in-usr-share-dns-roo.patch
     2026-01-26 20:53:48.000000000 +0100
@@ -3,9 +3,6 @@
 Subject: Debian default root.key resides in /usr/share/dns/root.key
 
 ---
- openpgpkey | 4 ++--
- tlsa       | 2 +-
- 2 files changed, 3 insertions(+), 3 deletions(-)
 
 diff --git a/openpgpkey b/openpgpkey
 index e423817..d3b80ef 100755
@@ -29,16 +26,3 @@
                        for root in cauldron:
                                if os.path.isfile(root):
                                        rootanchor=root
-diff --git a/tlsa b/tlsa
-index ae925ea..1c6c949 100755
---- a/tlsa
-+++ b/tlsa
-@@ -37,7 +37,7 @@ from hashlib import sha256, sha512
- from ipaddress import IPv4Address, IPv6Address
- 
- ROOTKEY="none"
--cauldron = ( "/var/lib/unbound/root.anchor", "/var/lib/unbound/root.key", 
"/etc/unbound/root.key" )
-+cauldron = ( "/usr/share/dns/root.key", "/var/lib/unbound/root.anchor", 
"/var/lib/unbound/root.key", "/etc/unbound/root.key" )
- for root in cauldron:
-       if os.path.isfile(root):
-               ROOTKEY=root
diff -Nru 
hash-slinger-3.1/debian/patches/0001-fix-generic-TLSA-record-generation.patch 
hash-slinger-3.5/debian/patches/0001-fix-generic-TLSA-record-generation.patch
--- 
hash-slinger-3.1/debian/patches/0001-fix-generic-TLSA-record-generation.patch   
    2023-10-05 16:36:07.000000000 +0200
+++ 
hash-slinger-3.5/debian/patches/0001-fix-generic-TLSA-record-generation.patch   
    1970-01-01 01:00:00.000000000 +0100
@@ -1,34 +0,0 @@
-From e3bec6e2a6b1bda7c52b4c585474fd7cc23ab643 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Charaoui?= <[email protected]>
-Date: Wed, 4 Oct 2023 22:05:26 -0400
-Subject: [PATCH] fix generic TLSA record generation
-Applied-Upstream: 
https://github.com/letoams/hash-slinger/commit/0bb0dba91c51d367d9a37297f13e07f33c01bfdc
-
-It seems like the calculation for the TLSA record never really worked,
-as we're doing float division here on the `len()` field. In our case,
-that field returned `35.0` which is not valid in our environment.
-
-Doing an integer division gives the correct result in most cases, I
-believe.
-
-Closes: #45
----
- tlsa | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/tlsa b/tlsa
-index cea7230..ec97150 100755
---- a/tlsa
-+++ b/tlsa
-@@ -513,7 +513,7 @@ class TLSARecord:
-       def getRecord(self, generic=False):
-               """Returns the RR string of this TLSARecord, either in rfc 
(default) or generic format"""
-               if generic:
--                      return '%s IN TYPE52 \# %s %s%s%s%s' % (self.name, 
(len(self.cert)/2)+3 , self._toHex(self.usage), self._toHex(self.selector), 
self._toHex(self.mtype), self.cert)
-+                      return '%s IN TYPE52 \# %s %s%s%s%s' % (self.name, 
(len(self.cert)//2)+3 , self._toHex(self.usage), self._toHex(self.selector), 
self._toHex(self.mtype), self.cert)
-               return '%s IN TLSA %s %s %s %s' % (self.name, self.usage, 
self.selector, self.mtype, self.cert)
- 
-       def _toHex(self, val):
--- 
-2.39.2
-
diff -Nru hash-slinger-3.1/debian/patches/series 
hash-slinger-3.5/debian/patches/series
--- hash-slinger-3.1/debian/patches/series      2023-10-05 16:36:07.000000000 
+0200
+++ hash-slinger-3.5/debian/patches/series      2026-01-26 20:53:48.000000000 
+0100
@@ -1,2 +1 @@
 0001-Debian-default-root.key-resides-in-usr-share-dns-roo.patch
-0001-fix-generic-TLSA-record-generation.patch
diff -Nru hash-slinger-3.1/fedora/hash-slinger.spec 
hash-slinger-3.5/fedora/hash-slinger.spec
--- hash-slinger-3.1/fedora/hash-slinger.spec   2021-01-14 11:10:30.000000000 
+0100
+++ hash-slinger-3.5/fedora/hash-slinger.spec   2026-01-26 17:44:59.000000000 
+0100
@@ -1,16 +1,16 @@
 Summary: Generate and verify various DNS records such as SSHFP, TLSA and 
OPENPGPKEY
 Name: hash-slinger
-Version: 3.1
-Release: 1%{?dist}
+Version: 3.5
+Release: 2%{?dist}
 License: GPLv2+
-Url:  http://people.redhat.com/pwouters/%{name}/
-Source:  http://people.redhat.com/pwouters/%{name}/%{name}-%{version}.tar.gz
+Url:  https://github.com/letoams/%{name}/
+Source:  %{url}archive/%{version}/%{name}-%{version}.tar.gz
 # Only to regenerate the man page, which is shipped per default
 # Buildrequires: xmlto
-BuildRequires: python3-devel
+BuildRequires: python3-devel, make
 Requires: python3 >= 3.4
 Requires: python3-dns, python3-unbound
-Requires: openssh-clients >= 4, python3-m2crypto, python3-gnupg >= 0.3.7
+Requires: openssh-clients >= 4, python3-cryptography, python3-gnupg >= 0.3.7
 BuildArch: noarch
 Obsoletes: sshfp < 2.0
 Provides: sshfp  = %{version}
@@ -20,30 +20,44 @@
 
 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
+tlsa        Generate RFC-6698 TLSA DNS records via TLS
+openpgpkey  Generate RFC-7929 OPENPGP DNS records from OpenPGP
             keyrings
 ipseckey    Generate RFC-4025 IPSECKEY DNS records on Libreswan
-           IPsec servers
+            IPsec servers
 
 This package has incorporated the old 'sshfp' and 'swede' commands/packages
 
 %prep
-%setup -q 
+%autosetup
 
 %build
-make all
+%make_build all
 
 %install
-rm -rf ${RPM_BUILD_ROOT}
-make DESTDIR=${RPM_BUILD_ROOT} install
+%make_install
 
 %files 
-%doc BUGS CHANGES README COPYING
+%license COPYING
+%doc BUGS CHANGES README
 %{_bindir}/*
 %doc %{_mandir}/man1/*
 
 %changelog
+* Sun Jan 09 2022 Frank Crawford <[email protected]> - 3.1-2
+- Update spec file following review
+
+* Sat Sep 25 2021 Frank Crawford <[email protected]> - 3.1-1
+- Updated to 3.1
+- Add BuildRequires make
+- Clean up spec file for Fedora review
+
+* Sun Nov 03 2019 Frank Crawford <[email protected]> - 3.0-1
+- Update to Python3
+
+* Sat Apr 13 2019 Paul Wouters <[email protected]> - 2.8-1
+- Remove Requires: for python-argparse which is now part of python core
+
 * Wed Sep 21 2016 Paul Wouters <[email protected]> - 2.7-3
 - Remove Requires: for python-argparse which is now part of python core
 
diff -Nru hash-slinger-3.1/ipseckey hash-slinger-3.5/ipseckey
--- hash-slinger-3.1/ipseckey   2021-01-14 11:10:30.000000000 +0100
+++ hash-slinger-3.5/ipseckey   2026-01-26 17:44:59.000000000 +0100
@@ -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 = "3.1"
+VERSION = "3.5"
 
 import os
 import sys
diff -Nru hash-slinger-3.1/openpgpkey hash-slinger-3.5/openpgpkey
--- hash-slinger-3.1/openpgpkey 2021-01-14 11:10:30.000000000 +0100
+++ hash-slinger-3.5/openpgpkey 2026-01-26 17:44:59.000000000 +0100
@@ -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="3.1"
+VERSION="3.5"
 OPENPGPKEY=61
 
 import sys
@@ -37,7 +37,7 @@
 
 def asctohex(s):
     empty = '' # I use this construct because I find ''.join() too dense
-    return empty.join(['%02x' % ord(c) for c in s]) # the %02 pads when needed
+    return empty.join(['%02x' % c for c in s]) # the %02 pads when needed
 
 def createOPENPGPKEY(email, gpgdisk, keyid, output, debug):
        ekey64 = "".join(gpgdisk.export_keys(keyid,minimal=True, secret=False, 
armor=True).split("\n")[2:-3])
@@ -51,7 +51,7 @@
                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(r"%s._openpgpkey.%s. IN TYPE61 \# %s %s" % (euser, 
domain, len(ekey), asctohex(ekey)))
 
 def sha256trunc(data):
        """Compute SHA2-256 hash truncated to 28 octets."""
@@ -169,11 +169,12 @@
                                                sys.exit(1)
                                if not args.uid and not args.keyid:
                                        pubkey = gpgnet.export_keys(args.email, 
minimal=True)
-                                       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("# %s"%id, 
file=sys.stderr)
-                                       sys.exit(1)
+                                       if not pubkey:
+                                               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("# %s"%id, 
file=sys.stderr)
+                                               sys.exit(1)
 
                                pubkey = pubkey.replace("Version:","Comment: %s 
key obtained from DNS\nVersion:"%args.email)
                                if args.insecure:
diff -Nru hash-slinger-3.1/openpgpkey.1 hash-slinger-3.5/openpgpkey.1
--- hash-slinger-3.1/openpgpkey.1       2021-01-14 11:10:30.000000000 +0100
+++ hash-slinger-3.5/openpgpkey.1       2026-01-26 17:44:59.000000000 +0100
@@ -90,15 +90,15 @@
 openpgpkey \-\-create paul@nohats\&.ca
 .SH "SEE ALSO"
 .PP
-draft\-ietf\-dane\-openpgpkey
+RFC-7929
 .PP
-\m[blue]\fBhttp://people\&.redhat\&.com/pwouters/hash\-slinger/\fR\m[]
+\m[blue]\fBhttps://github\&.com/letoams/hash\-slinger\fR\m[]
 .SH "AUTHORS"
 .PP
 Paul Wouters <pwouters@redhat\&.com>
 .SH "COPYRIGHT"
 .PP
-Copyright 2014\-2015
+Copyright 2014\-2022
 .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
diff -Nru hash-slinger-3.1/openpgpkey.1.xml hash-slinger-3.5/openpgpkey.1.xml
--- hash-slinger-3.1/openpgpkey.1.xml   2021-01-14 11:10:30.000000000 +0100
+++ hash-slinger-3.5/openpgpkey.1.xml   2026-01-26 17:44:59.000000000 +0100
@@ -103,9 +103,9 @@
 
 <refsect1 id='see_also'><title>SEE ALSO</title>
 
-<para>draft-ietf-dane-openpgpkey</para>
+<para>RFC-7929</para>
 
-<para><ulink 
url='http://people.redhat.com/pwouters/hash-slinger/'>http://people.redhat.com/pwouters/hash-slinger/</ulink></para>
+<para><ulink 
url='https://github.com/letoams/hash-slinger'>https://github.com/letoams/hash-slinger</ulink></para>
 </refsect1>
 
 <refsect1 id='authors'><title>AUTHORS</title>
@@ -113,7 +113,7 @@
 </refsect1>
 
 <refsect1 id='copyright'><title>COPYRIGHT</title>
-<para>Copyright 2014-2015</para>
+<para>Copyright 2014-2022</para>
 
 <para>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
diff -Nru hash-slinger-3.1/README hash-slinger-3.5/README
--- hash-slinger-3.1/README     2021-01-14 11:10:30.000000000 +0100
+++ hash-slinger-3.5/README     2026-01-26 17:44:59.000000000 +0100
@@ -4,7 +4,7 @@
 
 sshfp          Generate RFC-4255 SSHFP DNS records from known_hosts
                files or ssh-keyscan
-tlsa           Generate RFC-6698  TLSA DNS records via TLS
+tlsa           Generate RFC-6698 TLSA DNS records via TLS
 openpgpkey     Generate RFC-7929 OPENPGPKEY DNS records using gpg
 ipseckey       Generate RFC-4025 IPSECKEY DNS records on Libreswan
                IPsec servers
@@ -17,20 +17,21 @@
 
 Contributors:
 
-Chrisopher Olah <[email protected]>
-James Brown <[email protected]>
-Patrick Uiterwijk <[email protected]>
-Gerald Turner <[email protected]>
-Ondřej Surý <[email protected]>
-Jan Vcelak <[email protected]>
-Dirk Stöcker <[email protected]>
-Frank Crawford <[email protected]>
+Chrisopher Olah
+James Brown
+Patrick Uiterwijk
+Gerald Turner
+Ondřej Surý
+Jan Vcelak
+Dirk Stöcker
+Frank Crawford
+Bastian Germann
 
 REQUIREMENTS:
 
 python-dns     http://www.dnspython.org/
 python-gnupg   http://pythonhosted.org/python-gnupg/
-m2crypto       http://gitlab.com/m2crypto/m2crypto/
+cryptography   https://cryptography.io/
 unbound-python http://www.unbound.net/
 ssh-keygen     from openssh
 gpg            from gnupg
diff -Nru hash-slinger-3.1/sshfp hash-slinger-3.5/sshfp
--- hash-slinger-3.1/sshfp      2021-01-14 11:10:30.000000000 +0100
+++ hash-slinger-3.5/sshfp      2026-01-26 17:44:59.000000000 +0100
@@ -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 = "3.1"
+VERSION = "3.5"
 
 import os
 import sys
@@ -172,7 +172,7 @@
                salt = base64.b64decode(salt64)
                hash = base64.b64decode(hash64)
                for host in hostnames:
-                       if hash == hmac.new(salt, host, hashlib.sha1).digest():
+                       if hash == hmac.new(salt, 
host.encode(encoding="utf-8"), hashlib.sha1).digest():
                                return host
        return ""
 
@@ -301,7 +301,6 @@
                        action="store",
                        dest="known_hosts",
                        metavar="KNOWN_HOSTS_FILE",
-                       default=DEFAULT_KNOWN_HOSTS_FILE,
                        help="obtain public ssh keys from the known_hosts file 
KNOWN_HOSTS_FILE")
        parser.add_option("-s", "--scan", 
                        action="store_true",
@@ -363,7 +362,7 @@
        (options, args) = parser.parse_args()
 
        # parse options
-       khfile = options.known_hosts
+       khfile = options.known_hosts or DEFAULT_KNOWN_HOSTS_FILE
        dodns = options.scan
        nameserver = ""
        domain = ""
@@ -373,6 +372,14 @@
        trailing = options.trailing_dot
        timeout = options.timeout
        algos = options.algo or ["rsa", "ecdsa", "ed25519", "xmss"]
+       for algo in algos:
+               cmd = ["ssh-keyscan", "-t", algo]
+               process = subprocess.Popen(cmd, stdout=subprocess.PIPE, 
stderr=subprocess.PIPE, universal_newlines=True)
+               (stdout, stderr) = process.communicate()
+               if "Unknown key type" in stderr:
+                       algos.remove(algo)
+                       if not quiet:
+                               print("WARNING: key type %s not supported" % 
algo, file=sys.stderr)
        digests = options.digest or ["sha256"]
        all_hosts = options.all_hosts
        port = options.port
@@ -384,7 +391,7 @@
        if not quiet and port != 22:
                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("WARNING: Ignoring known hosts option -k , -s was 
passed", file=sys.stderr)
+               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("ERROR: Cannot specify -n without -s and -a", 
file=sys.stderr)
                sys.exit(1)
diff -Nru hash-slinger-3.1/sshfp.1 hash-slinger-3.5/sshfp.1
--- hash-slinger-3.1/sshfp.1    2021-01-14 11:10:30.000000000 +0100
+++ hash-slinger-3.5/sshfp.1    2026-01-26 17:44:59.000000000 +0100
@@ -32,16 +32,17 @@
 .SH "SYNTAX"
 .PP
 sshfp [\fB\-k\fR
-<\fIknownhosts_file\fR>] [\fB\-d\fR] [\fB\-a\fR] [\fB\-\-type\fR
+<\fIknownhosts_file\fR>] [\fB\-d\fR] [\fB\-a\fR] [\fB\-t\fR
 <algo>] [\fB\-\-digest\fR
 <digest>] [<\fIhost1\fR> [\fIhost2 \&.\&.\&.]\fR]
 .PP
 sshfp
 \fB\-s\fR
 [\fB\-p\fR
-<\fIport\fR>] [\fB\-d\fR] [\fB\-a\fR] [\fB\-\-type\fR
+<\fIport\fR>] [\fB\-d\fR] [\fB\-a\fR] [\fB\-t\fR
 <algo>] [\fB\-\-digest\fR
-<digest>] [\fB\-n <nameserver\fR>\fI] <domain1\fR> [\fIdomain2\fR] 
<\fIhost1\fR> [\fIhost2 \&.\&.\&.\fR] >
+<digest>] [\fB\-n\fR
+<\fInameserver\fR>] <\fIdomain1\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 transfers (AXFR), an entire domain can 
be processed for all its A records\&.
@@ -49,12 +50,12 @@
 .PP
 \fB\-s / \-\-scan\fR <\fIhostname1\fR> [hostname2 \&.\&.\&.]
 .RS 4
-Scan hosts or domain for public SSH keys using ssh\-keyscan
+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
+Obtain public SSH keys from a known_hosts file\&. Defaults to using 
~/\&.ssh/known_hosts\&.
 .RE
 .PP
 \fB\-a / \-\-all\fR
@@ -67,9 +68,19 @@
 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\-\-digest\fR <\fIdigest\fR>
+.RS 4
+Fingerprint hash function (may be specified more than once, default 
sha1,sha256)\&.
+.RE
+.PP
+\fB\-n / \-\-nameserver\fR <\fInameserver\fR>
+.RS 4
+Nameserver to use for AXFR (only valid with \-s \-a)\&.
+.RE
+.PP
 \fB\-o / \-\-output\fR <\fIfilename\fR>
 .RS 4
-Write to filename instead of stdout
+Write to filename instead of stdout\&.
 .RE
 .PP
 \fB\-p / \-\-port\fR <\fIportnumber\fR>
@@ -82,6 +93,16 @@
 Output help information and exit\&.
 .RE
 .PP
+\fB\-T / \-\-timeout\fR <\fIseconds\fR>
+.RS 4
+Scanning timeout, in seconds (default 5)\&.
+.RE
+.PP
+\fB\-t / \-\-type\fR <\fIalgo\fR>
+.RS 4
+Key type to fetch (may be specified more than once, default 
rsa,ecdsa,ed25519,dsa,xmss)\&.
+.RE
+.PP
 \fB\-v / \-\-version\fR
 .RS 4
 Output version information and exit\&.
@@ -89,7 +110,7 @@
 .PP
 \fB\-q / \-\-quiet\fR
 .RS 4
-Output less miscellany to stderr
+Output less miscellany to stderr\&.
 .RE
 .SH "FILES"
 .PP
@@ -123,7 +144,9 @@
 sshfp \-a \-d \-d nohats\&.ca \-n ns0\&.nohats\&.ca >> 
/var/named/primary/nohats\&.ca
 .SH "SEE ALSO"
 .PP
-\fBssh-keyscan\fR(1)\fBssh\fR(1)\fBtlsa\fR(1)
+\fBssh-keyscan\fR(1)
+\fBssh\fR(1)
+\fBtlsa\fR(1)
 and RFC\-4255
 .SH "AUTHORS"
 .PP
diff -Nru hash-slinger-3.1/sshfp.1.xml hash-slinger-3.5/sshfp.1.xml
--- hash-slinger-3.1/sshfp.1.xml        2021-01-14 11:10:30.000000000 +0100
+++ hash-slinger-3.5/sshfp.1.xml        2026-01-26 17:44:59.000000000 +0100
@@ -17,10 +17,9 @@
 <!-- body begins here -->
 
 <refsect1 id='syntax'><title>SYNTAX</title>
-<para>sshfp [<option>-k</option> &lt;<emphasis 
remap='I'>knownhosts_file</emphasis>&gt;] [<option>-d</option>] 
[<option>-a</option>] [<option>--type</option> &lt;algo&gt;] 
[<option>--digest</option> &lt;digest&gt;] [&lt;<emphasis 
remap='I'>host1</emphasis>&gt; [<emphasis remap='I'>host2 ...]</emphasis>]
-</para>
+<para>sshfp [<option>-k</option> &lt;<emphasis 
remap='I'>knownhosts_file</emphasis>&gt;] [<option>-d</option>] 
[<option>-a</option>] [<option>-t</option> &lt;algo&gt;] 
[<option>--digest</option> &lt;digest&gt;] [&lt;<emphasis 
remap='I'>host1</emphasis>&gt; [<emphasis remap='I'>host2 
...]</emphasis>]</para>
 <!-- .br -->
-<para>sshfp <option>-s</option> [<option>-p</option> &lt;<emphasis 
remap='I'>port</emphasis>&gt;] [<option>-d</option>] [<option>-a</option>] 
[<option>--type</option> &lt;algo&gt;] [<option>--digest</option> 
&lt;digest&gt;] [<option>-n &lt;nameserver</option>&gt;<emphasis remap='P->I'>] 
&lt;domain1</emphasis>&gt; [<emphasis remap='I'>domain2</emphasis>] 
&lt;<emphasis remap='I'>host1</emphasis>&gt; [<emphasis remap='I'>host2 
...</emphasis>] &gt;</para>
+<para>sshfp <option>-s</option> [<option>-p</option> &lt;<emphasis 
remap='I'>port</emphasis>&gt;] [<option>-d</option>] [<option>-a</option>] 
[<option>-t</option> &lt;algo&gt;] [<option>--digest</option> &lt;digest&gt;] 
[<option>-n</option> &lt;<emphasis remap='I'>nameserver</emphasis>&gt;] 
&lt;<emphasis remap='I'>domain1</emphasis>&gt; [<emphasis 
remap='I'>domain2</emphasis>] &lt;<emphasis remap='I'>host1</emphasis>&gt; 
[<emphasis remap='I'>host2 ...</emphasis>]</para>
 </refsect1>
 
 <refsect1 id='description'><title>DESCRIPTION</title>
@@ -37,13 +36,13 @@
   <varlistentry>
   <term><option>-s / --scan</option> &lt;<emphasis 
remap='I'>hostname1</emphasis>&gt; [hostname2 ...]</term>
   <listitem>
-<para>Scan hosts or domain for public SSH keys using ssh-keyscan</para> 
+<para>Scan hosts or domain for public SSH keys using ssh-keyscan.</para>
   </listitem>
   </varlistentry>
   <varlistentry>
   <term><option>-k / --knownhosts &lt;</option><emphasis 
remap='I'>knownhosts_file</emphasis><emphasis remap='P->B'>&gt; 
&lt;</emphasis><emphasis remap='I'>hostname1</emphasis><emphasis 
remap='P->B'>&gt; [hostname2 ...]</emphasis></term>
   <listitem>
-<para>Obtain public SSH keys from a known_hosts file. Defaults to using 
~/.ssh/known_hosts</para>
+<para>Obtain public SSH keys from a known_hosts file. Defaults to using 
~/.ssh/known_hosts.</para>
   </listitem>
   </varlistentry>
   <varlistentry>
@@ -67,9 +66,21 @@
   </listitem>
   </varlistentry>
   <varlistentry>
+  <term><option>--digest</option> &lt;<emphasis 
remap='I'>digest</emphasis>&gt;</term>
+  <listitem>
+<para>Fingerprint hash function (may be specified more than once, default 
sha1,sha256).</para>
+  </listitem>
+  </varlistentry>
+  <varlistentry>
+  <term><option>-n / --nameserver</option> &lt;<emphasis 
remap='I'>nameserver</emphasis>&gt;</term>
+  <listitem>
+<para>Nameserver to use for AXFR (only valid with -s -a).</para>
+  </listitem>
+  </varlistentry>
+  <varlistentry>
   <term><option>-o / --output</option> &lt;<emphasis 
remap='I'>filename</emphasis>&gt;</term>
   <listitem>
-<para>Write to filename instead of stdout</para>
+<para>Write to filename instead of stdout.</para>
   </listitem>
   </varlistentry>
   <varlistentry>
@@ -85,6 +96,18 @@
   </listitem>
   </varlistentry>
   <varlistentry>
+  <term><option>-T / --timeout</option> &lt;<emphasis 
remap='I'>seconds</emphasis>&gt;</term>
+  <listitem>
+<para>Scanning timeout, in seconds (default 5).</para>
+  </listitem>
+  </varlistentry>
+  <varlistentry>
+  <term><option>-t / --type</option> &lt;<emphasis 
remap='I'>algo</emphasis>&gt;</term>
+  <listitem>
+<para>Key type to fetch (may be specified more than once, default 
rsa,ecdsa,ed25519,dsa,xmss).</para>
+  </listitem>
+  </varlistentry>
+  <varlistentry>
   <term><option>-v / --version</option></term>
   <listitem>
 <para>Output version information and exit.</para>
@@ -93,7 +116,7 @@
   <varlistentry>
   <term><option>-q / --quiet</option></term>
   <listitem>
-<para>Output less miscellany to stderr</para>
+<para>Output less miscellany to stderr.</para>
   </listitem>
   </varlistentry>
 </variablelist>
diff -Nru hash-slinger-3.1/tlsa hash-slinger-3.5/tlsa
--- hash-slinger-3.1/tlsa       2021-01-14 11:10:30.000000000 +0100
+++ hash-slinger-3.5/tlsa       2026-01-26 17:44:59.000000000 +0100
@@ -23,21 +23,25 @@
 # with this program; if not, write to the Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
-VERSION="3.1"
+VERSION="3.5"
 
 import sys
 import os
 import socket
+import ssl
 import unbound
 import subprocess
 import re
-from M2Crypto import X509, SSL, BIO, m2
+from cryptography import x509
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.backends import default_backend
+from cryptography.x509.oid import ExtensionOID, NameOID
 from binascii import b2a_hex
 from hashlib import sha256, sha512
 from ipaddress import IPv4Address, IPv6Address
 
 ROOTKEY="none"
-cauldron = ( "/var/lib/unbound/root.anchor", "/var/lib/unbound/root.key", 
"/etc/unbound/root.key" )
+cauldron = ( "/var/lib/unbound/root.anchor", "/var/lib/unbound/root.key", 
"/etc/unbound/root.key", "/usr/share/dns/root.key" )
 for root in cauldron:
        if os.path.isfile(root):
                ROOTKEY=root
@@ -60,10 +64,10 @@
        if record.isValid:
                if record.selector == 0:
                        # Hash the Full certificate
-                       record.cert = getHash(certificate, record.mtype)
+                       record.cert = getHash(certificate, record.mtype, 
use_pubkey=False)
                else:
                        # Hash only the SubjectPublicKeyInfo
-                       record.cert = getHash(certificate.get_pubkey(), 
record.mtype)
+                       record.cert = getHash(certificate, record.mtype, 
use_pubkey=True)
 
        record.isValid(raiseException=True)
 
@@ -81,13 +85,13 @@
                certsleft -= 1
                if usage == 1 or usage == 3:
                        # The first cert is the end-entity cert
-                       print('Got a certificate for %s with Subject: %s' % 
(address, chaincert.get_subject()))
+                       print('Got a certificate for %s with Subject: %s' % 
(address, get_subject_str(chaincert)))
                        cert = chaincert
                        break
                else:
-                       if (usage == 0 and chaincert.check_ca()) or usage == 2:
+                       if (usage == 0 and check_ca(chaincert)) or usage == 2:
                                if certsleft: # don't ask for the last one
-                                       sys.stdout.write('Got a certificate 
with the following Subject:\n\t%s\nUse this as certificate to match? [y/N] ' % 
chaincert.get_subject())
+                                       sys.stdout.write('Got a certificate 
with the following Subject:\n\t%s\nUse this as certificate to match? [y/N] ' % 
get_subject_str(chaincert))
                                        input_ok = False
                                        while not input_ok:
                                                user_input = input()
@@ -99,7 +103,7 @@
                                                else:
                                                        
sys.stdout.write('Please answer Y or N')
                                else:
-                                       print('Using certificate with the 
following Subject:\n\t%s\n' % chaincert.get_subject())
+                                       print('Using certificate with the 
following Subject:\n\t%s\n' % get_subject_str(chaincert))
                                        cert = chaincert
                        if cert:
                                break
@@ -111,7 +115,6 @@
                else:
                        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):
@@ -222,50 +225,66 @@
        else:
                raise DNSLookupError('Unsuccessful DNS lookup or no data 
returned for rrtype %s (%s).' % (rrtypestr, rrtype))
 
-# identical to Connection.connect() except for the last parameter
-def sslStartTLSConnect(connection, addr, starttls=None):
-        connection.socket.connect(addr)
-        connection.addr = addr
-        if starttls:
-            # primitive method, no error checks yet
-            if starttls == "smtp":
-                data = connection.socket.recv(500)
-                connection.socket.send("EHLO M2Crypto\r\n".encode('ascii'))
-                data = connection.socket.recv(500)
-                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".encode('ascii'))
-                data = connection.socket.recv(500)
-            elif starttls == "ftp":
-                data = connection.socket.recv(500)
-                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".encode('ascii'))
-                data = connection.socket.recv(500)
-        connection.setup_ssl()
-        connection.set_connect_state()
-        ret = connection.connect_ssl()
-        check = getattr(connection, 'postConnectionCheck', 
connection.clientPostConnectionCheck)
-        if check is not None:
-            if not check(connection.get_peer_cert(), connection.addr[0]):
-                raise Checker.SSLVerificationError('post connection check 
failed')
-        return ret
+def get_subject_str(cert):
+       """Get a string representation of the certificate subject"""
+       return cert.subject.rfc4514_string()
 
-def getHash(certificate, mtype):
+def check_ca(cert):
+       """Check if a certificate is a CA certificate"""
+       try:
+               bc = 
cert.extensions.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS)
+               return bc.value.ca
+       except x509.ExtensionNotFound:
+               return False
+
+def get_subject_alt_names(cert):
+       """Get subject alternative names from a certificate"""
+       try:
+               ext = 
cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
+               return [name.value for name in ext.value if isinstance(name, 
x509.DNSName)]
+       except x509.ExtensionNotFound:
+               return []
+
+# Perform STARTTLS handshake on plain socket before wrapping with SSL
+def do_starttls_handshake(sock, starttls):
+       """Perform STARTTLS protocol handshake on a plain socket"""
+       if starttls == "smtp":
+               data = sock.recv(500)
+               sock.send("EHLO tlsa\r\n".encode('ascii'))
+               data = sock.recv(500)
+               sock.send("STARTTLS\r\n".encode('ascii'))
+               data = sock.recv(500)
+       elif starttls == "imap":
+               data = sock.recv(500)
+               sock.send(". STARTTLS\r\n".encode('ascii'))
+               data = sock.recv(500)
+       elif starttls == "ftp":
+               data = sock.recv(500)
+               sock.send("AUTH TLS\r\n".encode('ascii'))
+               data = sock.recv(500)
+       elif starttls == "pop3":
+               data = sock.recv(500)
+               sock.send("STLS\r\n".encode('ascii'))
+               data = sock.recv(500)
+
+def getHash(certificate, mtype, use_pubkey=False):
        """Hashes the certificate based on the mtype.
-       The certificate should be an M2Crypto.X509.X509 object (or the result 
of the get_pubkey() function on said object)
+       The certificate should be a cryptography x509.Certificate object.
+       If use_pubkey is True, hash only the SubjectPublicKeyInfo.
        """
-       certificate = certificate.as_der()
+       if use_pubkey:
+               cert_bytes = certificate.public_key().public_bytes(
+                encoding=serialization.Encoding.DER,
+                format=serialization.PublicFormat.SubjectPublicKeyInfo
+        )
+       else:
+               cert_bytes = 
certificate.public_bytes(serialization.Encoding.DER)
        if mtype == 0:
-               return certificate.hex()
+               return cert_bytes.hex()
        elif mtype == 1:
-               return sha256(certificate).hexdigest()
+               return sha256(cert_bytes).hexdigest()
        elif mtype == 2:
-               return sha512(certificate).hexdigest()
+               return sha512(cert_bytes).hexdigest()
        else:
                raise Exception('mtype should be 0,1,2')
 
@@ -303,17 +322,17 @@
 def verifyCertMatch(record, cert):
        """
        Verify the certificate with the record.
-       record should be a TLSARecord and cert should be a M2Crypto.X509.X509
+       record should be a TLSARecord and cert should be a cryptography 
x509.Certificate
        """
-       if not isinstance(cert, X509.X509):
+       if not isinstance(cert, x509.Certificate):
                return
        if not isinstance(record, TLSARecord):
                return
 
        if record.selector == 1:
-               certhash = getHash(cert.get_pubkey(), record.mtype)
+               certhash = getHash(cert, record.mtype, use_pubkey=True)
        else:
-               certhash = getHash(cert, record.mtype)
+               certhash = getHash(cert, record.mtype, use_pubkey=False)
 
        if not certhash:
                return
@@ -324,8 +343,8 @@
                return False
 
 def verifyCertNameWithHostName(cert, hostname, with_msg=False):
-       """Verify the name on the certificate with a hostname, we need this 
because we get the cert based on IP address and thusly cannot rely on M2Crypto 
to verify this"""
-       if not isinstance(cert, X509.X509):
+       """Verify the name on the certificate with a hostname, we need this 
because we get the cert based on IP address and thusly cannot rely on ssl to 
verify this"""
+       if not isinstance(cert, x509.Certificate):
                return
        if not isinstance(hostname, str):
                return
@@ -335,15 +354,15 @@
        hostname = hostname.lower()
 
        certnames = []
-       for entry in str(cert.get_subject()).lower().split("/"):
-               if entry.startswith("cn="):
-                       certnames.append(entry[3:])
-       try:
-               for value in re.split(", ?", 
cert.get_ext('subjectAltName').get_value().lower()):
-                       if value.startswith("dns:"):
-                               certnames.append(value[4:])
-       except:
-               pass
+       # Get CN from subject
+       for attr in cert.subject:
+               if attr.oid == NameOID.COMMON_NAME:
+                       certnames.append(attr.value.lower())
+       # Get SubjectAltName DNS entries
+       san_names = get_subject_alt_names(cert)
+       for name in san_names:
+               certnames.append(name.lower())
+
        if hostname in certnames:
                return True
        for certname in certnames:
@@ -351,7 +370,8 @@
                        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))
+               san_str = ', '.join(san_names) if san_names else 'None'
+               print('WARNING: Name on the certificate (Subject: %s, 
SubjectAltName: %s) doesn\'t match requested hostname (%s).' % 
(get_subject_str(cert), san_str, hostname))
        return False
 
 def checkChainLink(chain, record=None):
@@ -360,33 +380,35 @@
        matched = None
        for cert in chain:
                if previous_issuer:
-                       if not str(previous_issuer) == str(cert.get_subject()): 
# The chain cannot be valid
+                       if not previous_issuer == cert.subject: # The chain 
cannot be valid
                                chained = False
                                break
-               previous_issuer = cert.get_issuer()
+               previous_issuer = cert.issuer
                if record and verifyCertMatch(record, cert):
                        matched = cert
        return chained, matched
 
 def getLocalChain(filename, debug):
-       """Returns list of M2Crypto.X509.X509 objects and verification result"""
+       """Returns list of cryptography x509.Certificate objects and 
verification result"""
        chain = []
-       bio = BIO.openfile(filename)
-       Err = None
-       while True:
+       with open(filename, 'rb') as f:
+               pem_data = f.read()
+       # Parse all PEM certificates in the file
+       import re as regex
+       pem_certs = regex.findall(
+               rb'-----BEGIN CERTIFICATE-----[\s\S]*?-----END 
CERTIFICATE-----',
+               pem_data
+       )
+       for pem_cert in pem_certs:
                try:
-                       cptr = m2.x509_read_pem(bio._ptr())
+                       cert = x509.load_pem_x509_certificate(pem_cert, 
default_backend())
+                       chain.append(cert)
                except Exception as e:
-                       Err = e
-                       break;
-               if not cptr:
-                       break
-               chain.append(X509.X509(cptr, _pyfree=1))
+                       if debug:
+                               print('Error parsing certificate: %s' % e, 
file=sys.stderr)
+                       continue
        if not chain:
-               if Err:
-                       raise
-               else:
-                       raise Exception("Could not load %s" % filename)
+               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)
@@ -403,7 +425,8 @@
        names = ["PKIX-CA", "PKIX-EE", "DANE-TA", "DANE-EE"]
        return "Usage %d [%s]" % (usage, names[usage])
 
-def verifyChain(chain, verify_result, pre_exit, local, source):
+def verifyChain(record, chain, verify_result, pre_exit, local, source):
+       source = "%s for %s" % (record.getShortText(), source)
        if local:
                text_cert = "Local certificate"
                text_chain = "certificate in the local certificate chain"
@@ -429,7 +452,7 @@
                                        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))
                                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' % get_subject_str(matched))
                else:
                        print('FAIL: %s does not match the TLSA record (%s)' % 
(text_cert, source))
                        if pre_exit == 0: pre_exit = 2
@@ -445,7 +468,7 @@
                if not chained:
                        print("WARN: Certificates don't chain")
                if matched:
-                       if cert.check_ca():
+                       if check_ca(matched):
                                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))
                                else:
@@ -458,7 +481,7 @@
                        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))
                                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' % get_subject_str(matched))
                else:
                        print('FAIL (%s): No %s matches the TLSA record (%s)' % 
(ut, text_chain, source))
                        if pre_exit == 0: pre_exit = 2
@@ -495,7 +518,7 @@
        """When instanciated, this class contains all the fields of a TLSA 
record.
        """
        def __init__(self, name, usage, selector, mtype, cert):
-               """name is the name of the RR in the format: 
/^(_\d{1,5}|\*)\._(tcp|udp|sctp)\.([a-z0-9]*\.){2,}$/
+               r"""name is the name of the RR in the format: 
/^(_\d{1,5}|\*)\._(tcp|udp|sctp)\.([a-z0-9]*\.){2,}$/
                usage, selector and mtype should be an integer
                cert should be a hexidecimal string representing the 
certificate to be matched field
                """
@@ -513,7 +536,7 @@
        def getRecord(self, generic=False):
                """Returns the RR string of this TLSARecord, either in rfc 
(default) or generic format"""
                if generic:
-                       return '%s IN TYPE52 \# %s %s%s%s%s' % (self.name, 
(len(self.cert)/2)+3 , self._toHex(self.usage), self._toHex(self.selector), 
self._toHex(self.mtype), self.cert)
+                       return r'%s IN TYPE52 \# %s %s%s%s%s' % (self.name, 
(len(self.cert)//2)+3 , self._toHex(self.usage), self._toHex(self.selector), 
self._toHex(self.mtype), self.cert)
                return '%s IN TLSA %s %s %s %s' % (self.name, self.usage, 
self.selector, self.mtype, self.cert)
 
        def _toHex(self, val):
@@ -554,20 +577,23 @@
 
        def isNameValid(self):
                """Check if the name if in the correct format"""
-               if not 
re.match('^(_\d{1,5}|\*)\._(tcp|udp|sctp)\.([-a-z0-9]*\.){2,}$', self.name):
+               if not 
re.match(r'^(_\d{1,5}|\*)\._(tcp|udp|sctp)\.([-a-z0-9]*\.){2,}$', self.name):
                        return False
                return True
 
        def getProtocol(self):
                """Returns the protocol based on the name"""
-               return re.split('\.', self.name)[1][1:]
+               return re.split(r'\.', self.name)[1][1:]
 
        def getPort(self):
                """Returns the port based on the name"""
-               if re.split('\.', self.name)[0][0] == '*':
+               if re.split(r'\.', self.name)[0][0] == '*':
                        return '*'
                else:
-                       return re.split('\.', self.name)[0][1:]
+                       return re.split(r'\.', self.name)[0][1:]
+
+       def getShortText(self):
+               return "%d %d %d %.8s..." % (self.usage, self.selector, 
self.mtype, self.cert)
 
 class ARecord:
        """An object representing an A Record (IPv4 address)"""
@@ -703,9 +729,8 @@
                records = getTLSA(args.host, args.port, args.protocol, secure)
                if len(records) == 0:
                        sys.exit(1)
-
+               pre_exit = 0
                for record in records:
-                       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)
@@ -745,62 +770,68 @@
                        if args.certificate:
                                try:
                                        chain, verify_result = 
getLocalChain(args.certificate, args.debug)
-                                       pre_exit = verifyChain(chain, 
verify_result, pre_exit, True, args.certificate)
+                                       pre_exit = verifyChain(record, chain, 
verify_result, pre_exit, True, args.certificate)
                                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))
-                               # We do the certificate handling here, as 
M2Crypto keeps segfaulting when we do it in a method
-                               ctx = SSL.Context()
+                               # Create SSL context
+                               ctx = ssl.create_default_context()
                                if os.path.isfile(args.ca_cert):
-                                       if 
ctx.load_verify_locations(cafile=args.ca_cert) != 1: raise Exception('No CA 
cert')
+                                       
ctx.load_verify_locations(cafile=args.ca_cert)
                                elif os.path.exists(args.ca_cert):
-                                       if 
ctx.load_verify_locations(capath=args.ca_cert) != 1: raise Exception('No CA 
certs')
+                                       
ctx.load_verify_locations(capath=args.ca_cert)
                                else:
                                        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)
+                               ctx.check_hostname = False
+                               ctx.verify_mode = ssl.CERT_OPTIONAL
                                if isinstance(address, AAAARecord):
                                        sock = socket.socket(socket.AF_INET6, 
socket.SOCK_STREAM)
                                        sock.setsockopt(socket.SOL_SOCKET, 
socket.SO_REUSEADDR, 1)
                                else:
-                                       sock = None
-                               connection = SSL.Connection(ctx, sock=sock)
-                               try:
-                                       connection.set_tlsext_host_name(snihost)
-                                       if(args.debug):
-                                               print('Did set servername %s' % 
snihost)
-                               except AttributeError:
-                                       print('Could not set SNI (old M2Crypto 
version?)')
-                               except:
-                                       print('Could not set SNI')
+                                       sock = socket.socket(socket.AF_INET, 
socket.SOCK_STREAM)
+                                       sock.setsockopt(socket.SOL_SOCKET, 
socket.SO_REUSEADDR, 1)
                                try:
+                                       sock.connect((str(address), 
int(args.port)))
                                        if args.starttls:
-                                               sslStartTLSConnect(connection, 
(str(address), int(args.port)), args.starttls)
-                                               
#connection.connect((str(address), int(args.port)), args.starttls)
-                                       else:
-                                               
connection.connect((str(address), int(args.port)))
-                               #except TypeError:
-                               #       print 'Cannot connect to %s (old 
M2Crypto version not supporting start script?)' % address
-                               #       continue
-                               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)
+                                               do_starttls_handshake(sock, 
args.starttls)
+                                       connection = ctx.wrap_socket(sock, 
server_hostname=snihost)
+                                       if args.debug:
+                                               print('Did set servername %s' % 
snihost)
+                               except ssl.SSLCertVerificationError:
+                                       # Certificate verification failed but 
we still want to check TLSA
                                        pass
                                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()
+                               # Get peer certificate chain
+                               if hasattr(connection, 'get_peer_cert_chain'):
+                                       der_certs = 
connection.get_peer_cert_chain(binary_form=True)
+                                       if der_certs:
+                                               chain = 
[x509.load_der_x509_certificate(der_cert, default_backend()) for der_cert in 
der_certs]
+                                       else:
+                                               chain = None
+                               else:
+                                       # Fallback for Python < 3.13 without 
get_peer_cert_chain
+                                       der_cert = 
connection.getpeercert(binary_form=True)
+                                       if der_cert:
+                                               chain = 
[x509.load_der_x509_certificate(der_cert, default_backend())]
+                                       else:
+                                               chain = None
+                               if not chain:
+                                       print('Could not get certificate from 
%s' % address)
+                                       continue
+                               # Get verification result (0 means OK in ssl 
module when no exception)
+                               verify_result = 0  # If we got here without 
exception, verification passed
 
                                # Good, now let's verify
-                               pre_exit = verifyChain(chain, verify_result, 
pre_exit, False, address)
-                               # Cleanup, just in case
-                               connection.clear()
+                               pre_exit = verifyChain(record, chain, 
verify_result, pre_exit, False, address)
+                               # Cleanup
                                connection.close()
-                               ctx.close()
 
                        # END for address in addresses
                # END for record in records
@@ -848,44 +879,49 @@
                        for address in addresses:
                                if args.debug:
                                        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)
+                               # Create SSL context
+                               ctx = ssl.create_default_context()
+                               ctx.check_hostname = False
+                               ctx.verify_mode = ssl.CERT_NONE
                                if isinstance(address, AAAARecord):
                                        sock = socket.socket(socket.AF_INET6, 
socket.SOCK_STREAM)
                                        sock.setsockopt(socket.SOL_SOCKET, 
socket.SO_REUSEADDR, 1)
                                else:
-                                       sock = None
-                               connection = SSL.Connection(ctx, sock=sock)
-                               try:
-                                       connection.set_tlsext_host_name(snihost)
-                                       if(args.debug):
-                                               print('Did set servername %s' % 
snihost)
-                               except AttributeError:
-                                       print('Could not set SNI (old M2Crypto 
version?)')
-                               except:
-                                       print('Could not set SNI')
+                                       sock = socket.socket(socket.AF_INET, 
socket.SOCK_STREAM)
+                                       sock.setsockopt(socket.SOL_SOCKET, 
socket.SO_REUSEADDR, 1)
                                try:
+                                       sock.connect((str(address), 
int(connection_port)))
                                        if args.starttls:
-                                               sslStartTLSConnect(connection, 
(str(address), int(connection_port)), args.starttls)
-                                               
#connection.connect((str(address), int(connection_port)), args.starttls)
-                                       else:
-                                               
connection.connect((str(address), int(connection_port)))
-                               #except TypeError:
-                               #       print 'Cannot connect to %s (old 
M2Crypto version not supporting start script?)' % address
-                               #       continue
-                               except SSL.Checker.WrongHost:
-                                       pass
+                                               do_starttls_handshake(sock, 
args.starttls)
+                                       connection = ctx.wrap_socket(sock, 
server_hostname=snihost)
+                                       if args.debug:
+                                               print('Did set servername %s' % 
snihost)
                                except socket.error as e:
                                        print('Cannot connect to %s: %s' % 
(address, str(e)))
                                        continue
 
-                               chain = connection.get_peer_cert_chain()
+                               # Get peer certificate chain
+                               if hasattr(connection, 'get_peer_cert_chain'):
+                                       der_certs = 
connection.get_peer_cert_chain(binary_form=True)
+                                       if der_certs:
+                                               chain = 
[x509.load_der_x509_certificate(der_cert, default_backend()) for der_cert in 
der_certs]
+                                       else:
+                                               chain = None
+                               else:
+                                       # Fallback for Python < 3.13 without 
get_peer_cert_chain
+                                       der_cert = 
connection.getpeercert(binary_form=True)
+                                       if der_cert:
+                                               chain = 
[x509.load_der_x509_certificate(der_cert, default_backend())]
+                                       else:
+                                               chain = None
+                               if not chain:
+                                       print('Could not get certificate from 
%s' % address)
+                                       continue
+                               if not 
verifyCertNameWithHostName(cert=chain[0], hostname=str(args.host), 
with_msg=args.debug):
+                                       print('WARNING: Certificate name does 
not match the hostname')
                                genRecords(args.host, address, args.protocol, 
args.port, chain, args.output, args.usage, args.selector, args.mtype)
-                               # Cleanup the connection and context
-                               connection.clear()
+                               # Cleanup
                                connection.close()
-                               ctx.close()
 
                else: # Pass the path to the certificate to the genTLSA function
                        try:
diff -Nru hash-slinger-3.1/tlsa.1 hash-slinger-3.5/tlsa.1
--- hash-slinger-3.1/tlsa.1     2021-01-14 11:10:30.000000000 +0100
+++ hash-slinger-3.5/tlsa.1     2026-01-26 17:44:59.000000000 +0100
@@ -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\-\-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]
+tlsa [\fB\-h\fR] [\fB\-\-verify\fR] [\fB\-create\fR] [\fB\-\-version\fR] 
[\fB\-4\fR] [\fB\-6\fR] [\fB\-\-insecure\fR] [\fB\-\-resolvconf 
/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
@@ -113,7 +113,7 @@
 If neither create or verify is specified, create is used\&.
 .SH "REQUIREMENTS"
 .PP
-tlsa requires the following python libraries: unbound, m2crypto, argparse and 
ipaddr
+tlsa requires the following python libraries: unbound, cryptography, argparse 
and ipaddr
 .SH "BUGS"
 .PP
 ipv4/ipv6 handling
@@ -128,9 +128,9 @@
 tlsa \-\-create \-\-insecure fedoraproject\&.org
 .SH "SEE ALSO"
 .PP
-\fBsshfp\fR(1)\fBssh-keygen\fR(1)and RFC\-6698
+\fBsshfp\fR(1)\fB, ssh-keygen\fR(1) and RFC\-6698
 .PP
-\m[blue]\fBhttp://people\&.redhat\&.com/pwouters/hash\-slinger/\fR\m[]
+\m[blue]\fBhttps://github\&.com/letoams/hash\-slinger\fR\m[]
 .PP
 \m[blue]\fBhttp://os3sec\&.org/\fR\m[]
 .SH "AUTHORS"
diff -Nru hash-slinger-3.1/tlsa.1.xml hash-slinger-3.5/tlsa.1.xml
--- hash-slinger-3.1/tlsa.1.xml 2021-01-14 11:10:30.000000000 +0100
+++ hash-slinger-3.5/tlsa.1.xml 2026-01-26 17:44:59.000000000 +0100
@@ -19,7 +19,7 @@
 <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>--resolv.conf /PATH/TO/RESOLV.CONF</option>]
+           [<option>--resolvconf /PATH/TO/RESOLV.CONF</option>]
            [<option>--port PORT</option>] [<option>--starttls 
{auto,smtp,imap,pop3,ftp}</option>] 
            [<option>--protocol {tcp,udp,sctp}</option>] 
[<option>--only-rr</option>]
            [<option>--rootkey /PATH/TO/ROOT.KEY</option>]
@@ -134,7 +134,7 @@
 </refsect1>
 
 <refsect1 id='requirements'><title>REQUIREMENTS</title>
-<para>tlsa requires the following python libraries: unbound, m2crypto, 
argparse and ipaddr</para>
+<para>tlsa requires the following python libraries: unbound, cryptography, 
argparse and ipaddr</para>
 </refsect1>
 
 <refsect1 id='bugs'><title>BUGS</title>
@@ -156,7 +156,7 @@
 <refsect1 id='see_also'><title>SEE ALSO</title>
 
<para><citerefentry><refentrytitle>sshfp</refentrytitle><manvolnum>1</manvolnum></citerefentry>
 
<citerefentry><refentrytitle>ssh-keygen</refentrytitle><manvolnum>1</manvolnum></citerefentry>and
 RFC-6698</para>
 
-<para><ulink 
url='http://people.redhat.com/pwouters/hash-slinger/'>http://people.redhat.com/pwouters/hash-slinger/</ulink></para>
+<para><ulink 
url='https://github.com/letoams/hash-slinger'>https://github.com/letoams/hash-slinger</ulink></para>
 <para><ulink url='http://os3sec.org/'>http://os3sec.org/</ulink></para>
 </refsect1>
 

Reply via email to