From 6bb9ff371de2c15678aacb6c2697c462bdc80fd0 Mon Sep 17 00:00:00 2001 From: Richard Shaw Date: Thu, 2 Feb 2023 11:05:40 -0800 Subject: [PATCH 1/2] Add valid SSH keys with options for testing --- tests/fixtures/valid_ssh_keys | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/fixtures/valid_ssh_keys b/tests/fixtures/valid_ssh_keys index e648d67..e8c9663 100644 --- a/tests/fixtures/valid_ssh_keys +++ b/tests/fixtures/valid_ssh_keys @@ -1,3 +1,5 @@ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCkNCKyyUPvwwefhEpnD/Khuowbv+13Cv3shWY3ck3j+hbHLqjCGUB66/igg0Atf3giU7sZOdPrSN6xdX43+k6Gn73yzWFJkWyImKzgHW9uVrU7vo3puailY+yC6RpwNME= flynn@encom.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCkNCKyyUPvwwefhEpnD/Khuowbv+13Cv3shWY3ck3j+hbHLqjCGUB66/igg0Atf3giU7sZOdPrSN6xdX43+k6Gn73yzWFJkWyImKzgHW9uVrU7vo3puailY+yC6RpwNME= ssh-dss AAAAB3NzaC1kc3MAAACBAKMfNb7OtqUc5vje5UMvm5r1javDreL+EvACyU7N/BqO+FnqYx6RWQgp0mgDW5b+eOVBBwCfqn41TKHoXIlOsD3Rci7REBA87jmRbgQnZLSaT6BgVFWtEMI8SXG3EsmC5fXKHfX8j6W6f0Tuqeof4eaFW2RC6pYN3rvVGCvJ68FXAAAAFQDbmXw8U17CkJDvFOZ8KqehrahunQAAAIAKg/NxJVqLTJOM6qp7zI0WOiStotzclC5899w3zqJ446P/jLD5nILpnhbnUHnGmw53Yz6endWKHInsUurODPRZcjgOOd2lG5/RViegrI81YsQtczzIRNU86xL/28UkzzAy1IMeJWiH2vSrLLzx37YKZFH4MBjPhlsFtEIfHeR3dQAAAIEAlAMlPaHMIMEP+4IQP3qUhcZhI8UIYlSag2DqRjPry9jRL6uaYUYZAPC82Bvfi83TqJEhowqIBDFib4HDrgp9BJyTGfDZsHjRcLP15FYQdqIg8Fdo2ptsWGJa6EdFmj8RDIyaHsQ4a/JtnHUu+nC7DMYvTHliWl55UUVCSJzAyzs= +command="bbcp SRC",no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCkNCKyyUPvwwefhEpnD/Khuowbv+13Cv3shWY3ck3j+hbHLqjCGUB66/igg0Atf3giU7sZOdPrSN6xdX43+k6Gn73yzWFJkWyImKzgHW9uVrU7vo3puailY+yC6RpwNME= flynn@encom.com +command="bbcp SRC",no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQCkNCKyyUPvwwefhEpnD/Khuowbv+13Cv3shWY3ck3j+hbHLqjCGUB66/igg0Atf3giU7sZOdPrSN6xdX43+k6Gn73yzWFJkWyImKzgHW9uVrU7vo3puailY+yC6RpwNME= From e4651be0fa4d9be4583baf7ab8b55765147be7c3 Mon Sep 17 00:00:00 2001 From: Richard Shaw Date: Thu, 2 Feb 2023 11:38:34 -0800 Subject: [PATCH 2/2] Fix key validation to accept ssh keys with option arguments --- ssh_ldap_pubkey/__init__.py | 71 +++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/ssh_ldap_pubkey/__init__.py b/ssh_ldap_pubkey/__init__.py index 37cb428..5f33402 100644 --- a/ssh_ldap_pubkey/__init__.py +++ b/ssh_ldap_pubkey/__init__.py @@ -18,9 +18,76 @@ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ''' +def parse_key(pubkey): + """Split an SSH public key into its components. + + Keys can have the form: + [options] keytype keydata [keyname/comment] + + Arguments: + pubkey (str) The string to parse. + Returns: + tuple: A tuple of the parsed components containing + + - keytype (str): the ssh key type. + - data64 (str): the base64 encoded data. + - comment (str or None): the keyname/comment data. None if not present. + - options (str or None): the ssh key options. None if not present. + Raises: + ValueError: if pubkey cannot be parsed to a public key. + """ + valid_keytypes = [ + "ssh-dss", + "ssh-rsa", + "ecdsa-sha2-nistp256", + "ecdsa-sha2-nistp384", + "ecdsa-sha2-nistp521", + "ssh-ed25519", + ] + + # As the options field may contain spaces (within quoted strings) the + # parsing is not trivial. The strategy here is to find the key type field. + # Anything before it can be recombined into the options. The field + # immediately after must be the key data and the oen after that the comment. + comps = pubkey.split() + + # Locate the key type field as an anchor point + for keytype_ind, key_type in enumerate(comps): + if key_type in valid_keytypes: + break + else: + raise ValueError("public key does not contain valid key type.") + + # Recombine the key options + if comps[:keytype_ind]: + options = " ".join(comps[:keytype_ind]) + else: + options = None + comps = comps[(keytype_ind + 1):] + + # Get out the data field + if comps: + data64 = comps.pop(0) + else: + raise ValueError("Key does not contain a data field.") + + # Get the optional comment + if comps: + comment = comps[0] + else: + comment = None + + return (key_type, data64, comment, options) + def keyname(pubkey): - return pubkey.split()[-1] + comment = parse_key(pubkey)[2] + + if comment is None: + # Return the data as the keyname to match the previous behaviour + return data64 + else: + return comment def is_valid_openssh_pubkey(pubkey): @@ -34,7 +101,7 @@ def is_valid_openssh_pubkey(pubkey): bool: `True` if the given string is a valid key, `False` otherwise. """ try: - key_type, data64 = map(_encode, pubkey.split()[0:2]) + key_type, data64 = map(_encode, parse_key(pubkey)[:2]) except (ValueError, AttributeError): return False try: