Last active
December 14, 2015 11:38
-
-
Save maxtaco/5080023 to your computer and use it in GitHub Desktop.
Access hashed ssh known_hosts with paramiko.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os.path | |
import paramiko | |
from hashlib import sha1 | |
from hmac import HMAC | |
class KnownHostsRegistry (object): | |
""" | |
Read in the user's SSH known_hosts file, and perform lookups, | |
on either plaintext hostnames, or those that have been obscured | |
with known_hosts hashing (see here: http://nms.csail.mit.edu/projects/ssh/) | |
""" | |
def __init__ (self): | |
self.hosts = {} | |
self.hashes = [] | |
def load (self): | |
self.__loadOne(".ssh") | |
self.__loadOne("ssh") | |
if len(self.hosts) is 0: | |
print("Could not load any known SSH hosts; failure ahead") | |
self.__findHashes() | |
return self | |
def __findHashes(self): | |
for k in self.hosts.keys(): | |
parts = k.split("|") | |
if len(parts) is 4 and parts[1] is "1": | |
v = [ p.decode('base64') for p in parts[2:4]] | |
self.hashes.append(tuple([k] + v)) | |
def __loadOne(self, dir): | |
f = os.path.expanduser(os.path.join("~", dir, "known_hosts")) | |
if os.path.exists(f) and os.path.isfile(f): | |
tmp = paramiko.util.load_host_keys(f) | |
self.hosts.update(tmp) | |
def __findHashedHostname(self,hostname): | |
for (key,salt,res) in self.hashes: | |
hmac = HMAC(salt, None, sha1) | |
hmac.update(hostname) | |
ours = hmac.digest() | |
if ours == res: | |
return self.hosts.get(key) | |
return None | |
def lookup (self, hostname): | |
"""Find a row for the given hostname; either in plaintext | |
or as hashed hostname as per Jayeon's patch to ssh, which is | |
standard on Linux but not on Mac.""" | |
row = self.hosts.get(hostname) | |
if not row: | |
row = self.__findHashedHostname(hostname) | |
return row | |
def verify (self, hostname, theirs): | |
ok = False | |
err = None | |
typ = theirs.get_name() | |
row = self.lookup(hostname) | |
if row: | |
# get_name() return the type of key, like 'ssh-rsa' | |
# or 'ssh-dsa', etc... | |
ours = row.get(typ) | |
if not row: | |
err = "No keys found for hostname {h}" | |
elif not ours: | |
err = "No key of type {t} found for hostname {h}" | |
elif ours != theirs: | |
err = "Wrong {t} key found for hostname {h}: key changed!" | |
else: | |
ok = True | |
if err: | |
err = err.format(h=hostname, t=typ) | |
return (ok,err) | |
_s = None | |
def singleton(): | |
global _s | |
if not _s: _s = KnownHostsRegistry().load() | |
return _s | |
def lookup(hostname): | |
return singleton().lookup(hostname) | |
def verify(hostname, theirs): | |
return singleton().verify(hostname, theirs) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment