You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

78 lines
2.1 KiB
Python

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 to_string(self):
result = ''
if self.options is not None: result += self.options + ' '
result += f'{self.type} {self.key_b64}'
if self.comment is not None: result += ' ' + self.comment
return result
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.
def dump_file(keys: list[AuthorizedKey | str], f: IO[str]) -> None:
for rec in keys:
if isinstance(rec, AuthorizedKey):
rec = rec.to_string()
f.write(rec)
f.write('\n')