import base64 import hashlib import entry class KnownHostsSyntaxError(Exception): pass class HashedHostError(Exception): pass def process_line(line): """process_line(str) → [Entry] Given a string containing one line of .ssh/known_hosts file, create a list of Entries based on it.""" assert type(line) == str # Remove trailing newlines if line[-1] == '\n': line = line[:-1] # Just skip over empty lines if line == '': return [] # Each line has host(s), algorithm, public key, and possibly one # more optional field fields = line.split(' ') if len(fields) != 3 and len(fields) != 4: raise KnownHostsSyntaxError('Weird number of fields on a line (%i)' % len(fields)) hosts, algorithm, public_key = fields[0:3] # Generate public key fingerprint # The key is stored base64 encoded, so decode it first try: public_key_binary = base64.b64decode(public_key, validate = True) except (ValueError, base64.binascii.Error) as err: raise KnownHostsSyntaxError('Malformed public key: %s' % public_key) from err # Fingerprint is sha256 hash of the public key m = hashlib.sha256() m.update(public_key_binary) fingerprint = m.digest() # There can be several hosts separated with a comma entries = [] for host in hosts.split(','): # A host can't be empty if len(host) == 0: raise KnownHostsSyntaxError('An empty host') # If the host begins with '|' it's hashed # We cannot deal with those if host[0] == '|': raise HashedHostError('Cannot deal with hashed hosts') # If the host behins with '[' it's a nonstandard port # The format will be [domain]:port # Extractt both # Otherwise, default to port 22 if host[0] == '[': host_and_port = host[1:].split(']:') if len(host_and_port) != 2: raise KnownHostsSyntaxError('Unrecognized host format: ' + host) domain = host_and_port[0] try: port = int(host_and_port[1]) except ValueError: raise KnownHostsSyntaxError('Malformed port: %i' % port) else: domain = host port = 22 # Default to no comment entries.append(entry.create_entry(domain, port, fingerprint, '')) return entries