def test_non_root_ca(self): """Test error on non-root X.509 CA""" key = asyncssh.generate_private_key('ssh-rsa') cert = key.generate_x509_user_certificate(key, 'CN=a', 'CN=b') data = 'cert-authority ' + cert.export_certificate().decode('ascii') with self.assertRaises(ValueError): asyncssh.import_authorized_keys(data)
def test_errors(self): """Test various authorized key parsing errors""" tests = (('Bad key', 'xxx\n'), ('Unbalanced quote', 'xxx"\n'), ('Unbalanced backslash', 'xxx\\\n'), ('Missing option name', '=xxx\n'), ('Environment missing equals', 'environment="FOO"\n'), ('Environment missing variable name', 'environment="=xxx"\n'), ('PermitOpen missing colon', 'permitopen="xxx"\n'), ('PermitOpen non-integer port', 'permitopen="xxx:yyy"\n')) for msg, data in tests: with self.subTest(msg): with self.assertRaises(ValueError): asyncssh.import_authorized_keys(data)
async def start_server(cls): """Start an SSH server which supports security key authentication""" cls.addClassCleanup(unstub_sk, *stub_sk(cls._sk_devs)) cls._privkey = asyncssh.generate_private_key( cls._sk_alg, resident=cls._sk_resident, touch_required=cls._sk_touch_required) if cls._sk_host: if cls._sk_cert: cert = cls._privkey.generate_host_certificate( cls._privkey, 'localhost', principals=['127.0.0.1']) key = (cls._privkey, cert) else: key = cls._privkey return await cls.create_server(server_host_keys=[key]) else: options = [] if cls._sk_cert: options.append('cert-authority') if not cls._sk_auth_touch_required: options.append('no-touch-required') auth_keys = asyncssh.import_authorized_keys( ','.join(options) + (' ' if options else '') + cls._privkey.export_public_key().decode('utf-8')) return await cls.create_server(authorized_client_keys=auth_keys)
def test_subject_option_mismatch(self): """Test failed match on X.509 subject in options""" auth_keys = asyncssh.import_authorized_keys( 'subject=CN=cert1 ' + self.certlist[0]) result, _ = auth_keys.validate_x509( self.imported_certlist[0], '1.2.3.4') self.assertIsNone(result)
def test_subject_match(self): """Test match on X.509 subject name""" auth_keys = asyncssh.import_authorized_keys( 'x509v3-ssh-rsa subject=CN=cert0\n') result, _ = auth_keys.validate_x509( self.imported_certlist[0], '1.2.3.4') self.assertIsNotNone(result)
def test_errors(self): """Test various authorized key parsing errors""" tests = ( ('Bad key', 'xxx\n'), ('Unbalanced quote', 'xxx"\n'), ('Unbalanced backslash', 'xxx\\\n'), ('Missing option name', '=xxx\n'), ('Environment missing equals', 'environment="FOO"\n'), ('Environment missing variable name', 'environment="=xxx"\n'), ('PermitOpen missing colon', 'permitopen="xxx"\n'), ('PermitOpen non-integer port', 'permitopen="xxx:yyy"\n') ) for msg, data in tests: with self.subTest(msg): with self.assertRaises(ValueError): asyncssh.import_authorized_keys(data)
def start_server(cls): """Start an SSH server which supports public key authentication""" authorized_keys = asyncssh.import_authorized_keys( 'x509v3-ssh-rsa subject=OU=name\n') return (yield from cls.create_server(_PublicKeyServer, authorized_client_keys=authorized_keys, x509_trusted_certs=['ckey_x509_self.pub']))
async def begin_auth(self, username): """ Fetch and save user's keys for comparison later """ if username not in self.allowed_users: # Deny all users not explicitly allowed self.log.info(f"User {username} not in allowed_users, authentication denied") return True url = f'https://github.com/{username}.keys' async with aiohttp.ClientSession() as session, async_timeout.timeout(5): async with session.get(url) as response: keys = await response.text() if keys: self.conn.set_authorized_keys(asyncssh.import_authorized_keys(keys)) # Return true to indicate we always *must* authenticate return True
def build_keys(self, keys, from_file=False): """Build and import a list of authorized keys""" auth_keys = '# Comment line\n # Comment line with whitespace\n\n' for options in keys: options = options + ' ' if options else '' keynum = 1 if 'cert-authority' in options else 0 auth_keys += '%s%s' % (options, self.keylist[keynum]) if from_file: with open('authorized_keys', 'w') as f: f.write(auth_keys) return asyncssh.read_authorized_keys('authorized_keys') else: return asyncssh.import_authorized_keys(auth_keys)
def validate_public_key(self, username, key): """Called when the client presents a key for authentication.""" # Look up the peer address, to support the "from" key option. peer_addr = self.conn.get_extra_info('peername')[0] keystr = key.export_public_key().decode().strip() if config.ENABLE_AUTH: try: options = self.authorized_keys.validate( key, username, peer_addr) if 'denied' in options.get('access'): logging.debug("Rejecting key %s %s", keystr, username) return False except AttributeError: # options == None if not config.ALLOW_NEW_CLIENTS: return False options = default_access logging.info( 'Adding new key for user %s with default permissions' % username) options_str = ','.join([ f'{k}="{v}"' for k in default_access.keys() for v in default_access[k] ]) key_data = '%s %s %s@%s\n' % (options_str, keystr, username, peer_addr) if self.authorized_keys is None: self.authorized_keys = ( asyncssh.import_authorized_keys(key_data)) else: self.authorized_keys.load(key_data) with open(config.AUTHORIZED_KEYS_FILE, 'a') as f: f.write(key_data) else: options = default_access logging.debug("Accepting key %s %s %s", str(options), keystr, username) self.process_key_options(options) return True
async def begin_auth(self, username): """ Fetch and save user's keys for comparison later """ if self.allowed_users is not None and username not in self.allowed_users: # Deny all users not explicitly allowed self.log.info( f"User {username} not in allowed_users, authentication denied") return True url = f'{self.instance_url}/{username}.keys' async with aiohttp.ClientSession() as session, async_timeout.timeout( 5): async with session.get(url) as response: keys = await response.text() if keys: # Remove comment fields from SSH keys, as asyncssh seems to choke on those keys = "\n".join(re.findall("^[^ ]+ [^ ]+", keys, flags=re.M)) self.conn.set_authorized_keys( asyncssh.import_authorized_keys(keys)) # Return true to indicate we always *must* authenticate return True
def test_cert_authority_with_subject(self): """Test error when cert-authority is used with subject""" with self.assertRaises(ValueError): asyncssh.import_authorized_keys( 'cert-authority x509v3-sign-rsa subject=CN=cert0\n')