1- # Copyright (c) 2015-2024 by Ron Frederick <ronf@timeheart.net> and others.
1+ # Copyright (c) 2015-2026 by Ron Frederick <ronf@timeheart.net> and others.
22#
33# This program and the accompanying materials are made available under
44# the terms of the Eclipse Public License v2.0 which accompanies this
2020
2121"""Parser for SSH authorized_keys files"""
2222
23+ from pathlib import PurePath
2324from typing import Dict , List , Mapping , Optional , Sequence
2425from typing import Set , Tuple , Union , cast
2526
3031except ImportError : # pragma: no cover
3132 _x509_available = False
3233
33- from .misc import ip_address , read_file
34+ from .misc import FilePath , OptionsParser , ip_address , read_file
3435from .pattern import HostPatternList , WildcardPatternList
3536from .public_key import KeyImportError , SSHKey
3637from .public_key import SSHX509Certificate , SSHX509CertificateChain
4041
4142_EntryOptions = Mapping [str , object ]
4243
43- class _SSHAuthorizedKeyEntry :
44+
45+ class _SSHAuthorizedKeyEntry (OptionsParser ):
4446 """An entry in an SSH authorized_keys list"""
4547
4648 def __init__ (self , line : str ):
49+ super ().__init__ ()
50+
4751 self .key : Optional [SSHKey ] = None
4852 self .cert : Optional [SSHX509Certificate ] = None
49- self .options : Dict [str , object ] = {}
5053
5154 try :
5255 self ._import_key_or_cert (line )
@@ -151,60 +154,6 @@ def _add_subject(self, option: str, value: str) -> None:
151154 'subject' : _add_subject
152155 }
153156
154- def _add_option (self ) -> None :
155- """Add an option value"""
156-
157- if self ._option .startswith ('=' ):
158- raise ValueError ('Missing option name in authorized_keys' )
159-
160- if '=' in self ._option :
161- option , value = self ._option .split ('=' , 1 )
162-
163- handler = self ._handlers .get (option )
164- if handler :
165- handler (self , option , value )
166- else :
167- values = cast (List [str ], self .options .setdefault (option , []))
168- values .append (value )
169- else :
170- self .options [self ._option ] = True
171-
172- def _parse_options (self , line : str ) -> str :
173- """Parse options in this entry"""
174-
175- self ._option = ''
176-
177- idx = 0
178- quoted = False
179- escaped = False
180-
181- for idx , ch in enumerate (line ):
182- if escaped :
183- self ._option += ch
184- escaped = False
185- elif ch == '\\ ' :
186- escaped = True
187- elif ch == '"' :
188- quoted = not quoted
189- elif quoted :
190- self ._option += ch
191- elif ch in ' \t ' :
192- break
193- elif ch == ',' :
194- self ._add_option ()
195- self ._option = ''
196- else :
197- self ._option += ch
198-
199- self ._add_option ()
200-
201- if quoted :
202- raise ValueError ('Unbalanced quote in authorized_keys' )
203- elif escaped :
204- raise ValueError ('Unbalanced backslash in authorized_keys' )
205-
206- return line [idx :].strip ()
207-
208157 def match_options (self , client_host : str , client_addr : str ,
209158 cert_principals : Optional [Sequence [str ]],
210159 cert_subject : Optional ['X509Name' ] = None ) -> bool :
@@ -277,7 +226,7 @@ def load(self, authorized_keys: str) -> None:
277226
278227 def validate (self , key : SSHKey , client_host : str , client_addr : str ,
279228 cert_principals : Optional [Sequence [str ]] = None ,
280- ca : bool = False ) -> Optional [Mapping [ str , object ] ]:
229+ ca : bool = False ) -> Optional [_EntryOptions ]:
281230 """Return whether a public key or CA is valid for authentication"""
282231
283232 for entry in self ._ca_entries if ca else self ._user_entries :
@@ -322,7 +271,7 @@ def import_authorized_keys(data: str) -> SSHAuthorizedKeys:
322271 return SSHAuthorizedKeys (data )
323272
324273
325- def read_authorized_keys (filelist : Union [str , Sequence [str ]]) -> \
274+ def read_authorized_keys (filelist : Union [FilePath , Sequence [FilePath ]]) -> \
326275 SSHAuthorizedKeys :
327276 """Read SSH authorized keys from a file or list of files
328277
@@ -331,16 +280,16 @@ def read_authorized_keys(filelist: Union[str, Sequence[str]]) -> \
331280
332281 :param filelist:
333282 The file or list of files to read the keys from.
334- :type filenlist : `str` or ` list` of `str`
283+ :type filelist : `PurePath`, ` str`, or a list of these
335284
336285 :returns: An :class:`SSHAuthorizedKeys` object
337286
338287 """
339288
340289 authorized_keys = SSHAuthorizedKeys ()
341290
342- if isinstance (filelist , str ):
343- files : Sequence [str ] = [filelist ]
291+ if isinstance (filelist , ( PurePath , str ) ):
292+ files : Sequence [FilePath ] = [filelist ]
344293 else :
345294 files = filelist
346295
0 commit comments