from collections import namedtuple import hashing # Entry(bytes[32], bytes[32], bytes[32], bytes[0…2¹⁶-1]) Entry = namedtuple('Entry', ['salt', 'hashed_host', 'fingerprint', 'comment']) class UnacceptableComment(Exception): pass def create_entry(domain, port, fingerprint, comment): """create_entry(str, u16, bytes[32], str) → Entry Given unprocessed host, a binary fingerprint and a comment, creates and entry describing it""" assert type(domain) == str assert type(port) == int and 0 <= port <= (1<<16) - 1 assert type(fingerprint) == bytes and len(fingerprint) == 32 assert type(comment) == str # We want to have domain names reasonably normalized. This is why we # convert all internationalized domain names to punycode and # lowercase all domains. # The reason the lowercasing happens after the punycoding is because # that way we don't have to worry about Unicode case mapping: in # case of IDN the IDNA codec handles that for us, and in case of an # ASCII domain it passes through the IDNA unmodified processed_host = domain.encode('idna').lower() # If the port is not :22, we store [host]:port instead if port != 22: processed_host = b'[%s]%i' % (processed_host, port) # Hash the host and store the salt salt, hashed_host = hashing.hash_host(processed_host) # Comment must not include newlines if '\n' in comment: raise UnacceptableComment('Comment contains newlines') comment_encoded = comment.encode('utf-8') # Comment may be at max 2¹⁶-1 bytes long if len(comment_encoded) >= 1<<16: raise UnacceptableComment('Comment length of %i bytes is too long' % len(comment_encoded)) return Entry(salt, hashed_host, fingerprint, comment_encoded)