commit 134cae1db32520325e9622d27de8554d276c5899 Author: Pavel 'LEdoian' Turinsky Date: Sun Jan 9 16:31:59 2022 +0100 Initial implementation It should work, nobody tested it, there are TODO's which will likely never be implemented :-) diff --git a/authorizedkeys/__init__.py b/authorizedkeys/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authorizedkeys/parser.py b/authorizedkeys/parser.py new file mode 100644 index 0000000..8111ef7 --- /dev/null +++ b/authorizedkeys/parser.py @@ -0,0 +1,63 @@ +from typing import IO + +# Copied from sshd(8) +key_types = [ + "sk-ecdsa-sha2-nistp256@openssh.com", + "ecdsa-sha2-nistp256", + "ecdsa-sha2-nistp384", + "ecdsa-sha2-nistp521", + "sk-ssh-ed25519@openssh.com", + "ssh-ed25519", + "ssh-dss", + "ssh-rsa", + ] + + +class AuthorizedKey: + def __init__(self, line): + self.original = line + line = line.strip() + if line.startswith('#') or line == '': + raise ValueError('This is not a key, it is a comment or an empty line.') + if line.split(' ')[0] not in key_types: + # The hard case: there are options at the beginning. + # It is too simple to code the state machine myself, so no library :-) + in_quotes = False + backslash_preceding = False + for idx, char in enumerate(line): + if char == '\"' and not backslash_preceeding: + in_quotes = not in_quotes + elif char == ' ' and not in_quotes: + break + else: + backslash_preceeding = char == '\\' + else: + raise ValueError('Badly formatted options not followed by a key.') + + if line[idx] != ' ': + raise ValueError('I am just broken.') + self.options = line[:idx] + line = line[idx+1:] + line = line.strip() # In case there are multiple spaces + else: + self.options = None + + # Now only the key follows, so this is simple + self.options = None + split = line.split(' ', maxsplit=2) + self.type = split[0] + self.key_b64 = split[1] + self.coment = split[2] if len(split) >= 3 else None + +def parse_file(f: IO[str]) -> list[AuthorizedKey | str]: + result = [] + for line in f: + stripped = line.strip() + if stripped.startswith('#') or stripped == '': + # This is a comment / empty line, should be preserved + result.append(line) + else: + result.append(AuthorizedKey(line)) + return result + +# TODO: Implement option parsing, key validation and decoding to bytes.