%PDF- %PDF-
Direktori : /lib/python3/dist-packages/twisted/conch/test/ |
Current File : //lib/python3/dist-packages/twisted/conch/test/test_checkers.py |
# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ Tests for L{twisted.conch.checkers}. """ import os from base64 import encodebytes from collections import namedtuple from io import BytesIO from typing import Optional cryptSkip: Optional[str] try: import crypt except ImportError: cryptSkip = "cannot run without crypt module" else: cryptSkip = None from zope.interface.verify import verifyObject from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse from twisted.cred.credentials import ( ISSHPrivateKey, IUsernamePassword, SSHPrivateKey, UsernamePassword, ) from twisted.cred.error import UnauthorizedLogin, UnhandledCredentials from twisted.internet.defer import Deferred from twisted.python import util from twisted.python.fakepwd import ShadowDatabase, UserDatabase from twisted.python.filepath import FilePath from twisted.python.reflect import requireModule from twisted.test.test_process import MockOS from twisted.trial.unittest import TestCase if requireModule("cryptography"): dependencySkip = None from twisted.conch import checkers from twisted.conch.error import NotEnoughAuthentication, ValidPublicKey from twisted.conch.ssh import keys from twisted.conch.test import keydata else: dependencySkip = "can't run without cryptography" if getattr(os, "geteuid", None) is not None: euidSkip = None else: euidSkip = "Cannot run without effective UIDs (questionable)" class HelperTests(TestCase): """ Tests for helper functions L{verifyCryptedPassword}, L{_pwdGetByName} and L{_shadowGetByName}. """ skip = cryptSkip or dependencySkip def setUp(self): self.mockos = MockOS() def test_verifyCryptedPassword(self): """ L{verifyCryptedPassword} returns C{True} if the plaintext password passed to it matches the encrypted password passed to it. """ password = "secret string" salt = "salty" crypted = crypt.crypt(password, salt) self.assertTrue( checkers.verifyCryptedPassword(crypted, password), "{!r} supposed to be valid encrypted password for {!r}".format( crypted, password ), ) def test_verifyCryptedPasswordMD5(self): """ L{verifyCryptedPassword} returns True if the provided cleartext password matches the provided MD5 password hash. """ password = "password" salt = "$1$salt" crypted = crypt.crypt(password, salt) self.assertTrue( checkers.verifyCryptedPassword(crypted, password), "{!r} supposed to be valid encrypted password for {}".format( crypted, password ), ) def test_refuteCryptedPassword(self): """ L{verifyCryptedPassword} returns C{False} if the plaintext password passed to it does not match the encrypted password passed to it. """ password = "string secret" wrong = "secret string" crypted = crypt.crypt(password, password) self.assertFalse( checkers.verifyCryptedPassword(crypted, wrong), "{!r} not supposed to be valid encrypted password for {}".format( crypted, wrong ), ) def test_pwdGetByName(self): """ L{_pwdGetByName} returns a tuple of items from the UNIX /etc/passwd database if the L{pwd} module is present. """ userdb = UserDatabase() userdb.addUser("alice", "secrit", 1, 2, "first last", "/foo", "/bin/sh") self.patch(checkers, "pwd", userdb) self.assertEqual(checkers._pwdGetByName("alice"), userdb.getpwnam("alice")) def test_pwdGetByNameWithoutPwd(self): """ If the C{pwd} module isn't present, L{_pwdGetByName} returns L{None}. """ self.patch(checkers, "pwd", None) self.assertIsNone(checkers._pwdGetByName("alice")) def test_shadowGetByName(self): """ L{_shadowGetByName} returns a tuple of items from the UNIX /etc/shadow database if the L{spwd} is present. """ userdb = ShadowDatabase() userdb.addUser("bob", "passphrase", 1, 2, 3, 4, 5, 6, 7) self.patch(checkers, "spwd", userdb) self.mockos.euid = 2345 self.mockos.egid = 1234 self.patch(util, "os", self.mockos) self.assertEqual(checkers._shadowGetByName("bob"), userdb.getspnam("bob")) self.assertEqual(self.mockos.seteuidCalls, [0, 2345]) self.assertEqual(self.mockos.setegidCalls, [0, 1234]) def test_shadowGetByNameWithoutSpwd(self): """ L{_shadowGetByName} returns L{None} if C{spwd} is not present. """ self.patch(checkers, "spwd", None) self.assertIsNone(checkers._shadowGetByName("bob")) self.assertEqual(self.mockos.seteuidCalls, []) self.assertEqual(self.mockos.setegidCalls, []) class SSHPublicKeyDatabaseTests(TestCase): """ Tests for L{SSHPublicKeyDatabase}. """ skip = euidSkip or dependencySkip def setUp(self) -> None: self.checker = checkers.SSHPublicKeyDatabase() self.key1 = encodebytes(b"foobar") self.key2 = encodebytes(b"eggspam") self.content = b"t1 " + self.key1 + b" foo\nt2 " + self.key2 + b" egg\n" self.mockos = MockOS() self.patch(util, "os", self.mockos) self.path = FilePath(self.mktemp()) assert isinstance(self.path.path, str) # text mode self.sshDir = self.path.child(".ssh") self.sshDir.makedirs() userdb = UserDatabase() userdb.addUser( "user", "password", 1, 2, "first last", self.path.path, "/bin/shell", ) self.checker._userdb = userdb # type: ignore def test_deprecated(self): """ L{SSHPublicKeyDatabase} is deprecated as of version 15.0 """ warningsShown = self.flushWarnings(offendingFunctions=[self.setUp]) self.assertEqual(warningsShown[0]["category"], DeprecationWarning) self.assertEqual( warningsShown[0]["message"], "twisted.conch.checkers.SSHPublicKeyDatabase " "was deprecated in Twisted 15.0.0: Please use " "twisted.conch.checkers.SSHPublicKeyChecker, " "initialized with an instance of " "twisted.conch.checkers.UNIXAuthorizedKeysFiles instead.", ) self.assertEqual(len(warningsShown), 1) def _testCheckKey(self, filename): self.sshDir.child(filename).setContent(self.content) user = UsernamePassword(b"user", b"password") user.blob = b"foobar" self.assertTrue(self.checker.checkKey(user)) user.blob = b"eggspam" self.assertTrue(self.checker.checkKey(user)) user.blob = b"notallowed" self.assertFalse(self.checker.checkKey(user)) def test_checkKey(self): """ L{SSHPublicKeyDatabase.checkKey} should retrieve the content of the authorized_keys file and check the keys against that file. """ self._testCheckKey("authorized_keys") self.assertEqual(self.mockos.seteuidCalls, []) self.assertEqual(self.mockos.setegidCalls, []) def test_checkKey2(self): """ L{SSHPublicKeyDatabase.checkKey} should retrieve the content of the authorized_keys2 file and check the keys against that file. """ self._testCheckKey("authorized_keys2") self.assertEqual(self.mockos.seteuidCalls, []) self.assertEqual(self.mockos.setegidCalls, []) def test_checkKeyAsRoot(self): """ If the key file is readable, L{SSHPublicKeyDatabase.checkKey} should switch its uid/gid to the ones of the authenticated user. """ keyFile = self.sshDir.child("authorized_keys") keyFile.setContent(self.content) # Fake permission error by changing the mode keyFile.chmod(0o000) self.addCleanup(keyFile.chmod, 0o777) # And restore the right mode when seteuid is called savedSeteuid = self.mockos.seteuid def seteuid(euid): keyFile.chmod(0o777) return savedSeteuid(euid) self.mockos.euid = 2345 self.mockos.egid = 1234 self.patch(self.mockos, "seteuid", seteuid) self.patch(util, "os", self.mockos) user = UsernamePassword(b"user", b"password") user.blob = b"foobar" self.assertTrue(self.checker.checkKey(user)) self.assertEqual(self.mockos.seteuidCalls, [0, 1, 0, 2345]) self.assertEqual(self.mockos.setegidCalls, [2, 1234]) def test_requestAvatarId(self): """ L{SSHPublicKeyDatabase.requestAvatarId} should return the avatar id passed in if its C{_checkKey} method returns True. """ def _checkKey(ignored): return True self.patch(self.checker, "checkKey", _checkKey) credentials = SSHPrivateKey( b"test", b"ssh-rsa", keydata.publicRSA_openssh, b"foo", keys.Key.fromString(keydata.privateRSA_openssh).sign(b"foo"), ) d = self.checker.requestAvatarId(credentials) def _verify(avatarId): self.assertEqual(avatarId, b"test") return d.addCallback(_verify) def test_requestAvatarIdWithoutSignature(self): """ L{SSHPublicKeyDatabase.requestAvatarId} should raise L{ValidPublicKey} if the credentials represent a valid key without a signature. This tells the user that the key is valid for login, but does not actually allow that user to do so without a signature. """ def _checkKey(ignored): return True self.patch(self.checker, "checkKey", _checkKey) credentials = SSHPrivateKey( b"test", b"ssh-rsa", keydata.publicRSA_openssh, None, None ) d = self.checker.requestAvatarId(credentials) return self.assertFailure(d, ValidPublicKey) def test_requestAvatarIdInvalidKey(self): """ If L{SSHPublicKeyDatabase.checkKey} returns False, C{_cbRequestAvatarId} should raise L{UnauthorizedLogin}. """ def _checkKey(ignored): return False self.patch(self.checker, "checkKey", _checkKey) d = self.checker.requestAvatarId(None) return self.assertFailure(d, UnauthorizedLogin) def test_requestAvatarIdInvalidSignature(self): """ Valid keys with invalid signatures should cause L{SSHPublicKeyDatabase.requestAvatarId} to return a {UnauthorizedLogin} failure """ def _checkKey(ignored): return True self.patch(self.checker, "checkKey", _checkKey) credentials = SSHPrivateKey( b"test", b"ssh-rsa", keydata.publicRSA_openssh, b"foo", keys.Key.fromString(keydata.privateDSA_openssh).sign(b"foo"), ) d = self.checker.requestAvatarId(credentials) return self.assertFailure(d, UnauthorizedLogin) def test_requestAvatarIdNormalizeException(self): """ Exceptions raised while verifying the key should be normalized into an C{UnauthorizedLogin} failure. """ def _checkKey(ignored): return True self.patch(self.checker, "checkKey", _checkKey) credentials = SSHPrivateKey(b"test", None, b"blob", b"sigData", b"sig") d = self.checker.requestAvatarId(credentials) def _verifyLoggedException(failure): errors = self.flushLoggedErrors(keys.BadKeyError) self.assertEqual(len(errors), 1) return failure d.addErrback(_verifyLoggedException) return self.assertFailure(d, UnauthorizedLogin) class SSHProtocolCheckerTests(TestCase): """ Tests for L{SSHProtocolChecker}. """ skip = dependencySkip def test_registerChecker(self): """ L{SSHProcotolChecker.registerChecker} should add the given checker to the list of registered checkers. """ checker = checkers.SSHProtocolChecker() self.assertEqual(checker.credentialInterfaces, []) checker.registerChecker( checkers.SSHPublicKeyDatabase(), ) self.assertEqual(checker.credentialInterfaces, [ISSHPrivateKey]) self.assertIsInstance( checker.checkers[ISSHPrivateKey], checkers.SSHPublicKeyDatabase ) def test_registerCheckerWithInterface(self): """ If a specific interface is passed into L{SSHProtocolChecker.registerChecker}, that interface should be registered instead of what the checker specifies in credentialIntefaces. """ checker = checkers.SSHProtocolChecker() self.assertEqual(checker.credentialInterfaces, []) checker.registerChecker(checkers.SSHPublicKeyDatabase(), IUsernamePassword) self.assertEqual(checker.credentialInterfaces, [IUsernamePassword]) self.assertIsInstance( checker.checkers[IUsernamePassword], checkers.SSHPublicKeyDatabase ) def test_requestAvatarId(self): """ L{SSHProtocolChecker.requestAvatarId} should defer to one if its registered checkers to authenticate a user. """ checker = checkers.SSHProtocolChecker() passwordDatabase = InMemoryUsernamePasswordDatabaseDontUse() passwordDatabase.addUser(b"test", b"test") checker.registerChecker(passwordDatabase) d = checker.requestAvatarId(UsernamePassword(b"test", b"test")) def _callback(avatarId): self.assertEqual(avatarId, b"test") return d.addCallback(_callback) def test_requestAvatarIdWithNotEnoughAuthentication(self): """ If the client indicates that it is never satisfied, by always returning False from _areDone, then L{SSHProtocolChecker} should raise L{NotEnoughAuthentication}. """ checker = checkers.SSHProtocolChecker() def _areDone(avatarId): return False self.patch(checker, "areDone", _areDone) passwordDatabase = InMemoryUsernamePasswordDatabaseDontUse() passwordDatabase.addUser(b"test", b"test") checker.registerChecker(passwordDatabase) d = checker.requestAvatarId(UsernamePassword(b"test", b"test")) return self.assertFailure(d, NotEnoughAuthentication) def test_requestAvatarIdInvalidCredential(self): """ If the passed credentials aren't handled by any registered checker, L{SSHProtocolChecker} should raise L{UnhandledCredentials}. """ checker = checkers.SSHProtocolChecker() d = checker.requestAvatarId(UsernamePassword(b"test", b"test")) return self.assertFailure(d, UnhandledCredentials) def test_areDone(self): """ The default L{SSHProcotolChecker.areDone} should simply return True. """ self.assertTrue(checkers.SSHProtocolChecker().areDone(None)) class UNIXPasswordDatabaseTests(TestCase): """ Tests for L{UNIXPasswordDatabase}. """ skip = cryptSkip or dependencySkip def assertLoggedIn(self, d: Deferred[bytes], username: bytes) -> None: """ Assert that the L{Deferred} passed in is called back with the value 'username'. This represents a valid login for this TestCase. @param d: a L{Deferred} from an L{IChecker.requestAvatarId} method. """ self.assertEqual(self.successResultOf(d), username) def test_defaultCheckers(self): """ L{UNIXPasswordDatabase} with no arguments has checks the C{pwd} database and then the C{spwd} database. """ checker = checkers.UNIXPasswordDatabase() def crypted(username, password): salt = crypt.crypt(password, username) crypted = crypt.crypt(password, "$1$" + salt) return crypted pwd = UserDatabase() pwd.addUser( "alice", crypted("alice", "password"), 1, 2, "foo", "/foo", "/bin/sh" ) # x and * are convention for "look elsewhere for the password" pwd.addUser("bob", "x", 1, 2, "bar", "/bar", "/bin/sh") spwd = ShadowDatabase() spwd.addUser("alice", "wrong", 1, 2, 3, 4, 5, 6, 7) spwd.addUser("bob", crypted("bob", "password"), 8, 9, 10, 11, 12, 13, 14) self.patch(checkers, "pwd", pwd) self.patch(checkers, "spwd", spwd) mockos = MockOS() self.patch(util, "os", mockos) mockos.euid = 2345 mockos.egid = 1234 cred = UsernamePassword(b"alice", b"password") self.assertLoggedIn(checker.requestAvatarId(cred), b"alice") self.assertEqual(mockos.seteuidCalls, []) self.assertEqual(mockos.setegidCalls, []) cred.username = b"bob" self.assertLoggedIn(checker.requestAvatarId(cred), b"bob") self.assertEqual(mockos.seteuidCalls, [0, 2345]) self.assertEqual(mockos.setegidCalls, [0, 1234]) def assertUnauthorizedLogin(self, d): """ Asserts that the L{Deferred} passed in is erred back with an L{UnauthorizedLogin} L{Failure}. This reprsents an invalid login for this TestCase. NOTE: To work, this method's return value must be returned from the test method, or otherwise hooked up to the test machinery. @param d: a L{Deferred} from an L{IChecker.requestAvatarId} method. @type d: L{Deferred} @rtype: L{None} """ self.failureResultOf(d, checkers.UnauthorizedLogin) def test_passInCheckers(self): """ L{UNIXPasswordDatabase} takes a list of functions to check for UNIX user information. """ password = crypt.crypt("secret", "secret") userdb = UserDatabase() userdb.addUser("anybody", password, 1, 2, "foo", "/bar", "/bin/sh") checker = checkers.UNIXPasswordDatabase([userdb.getpwnam]) self.assertLoggedIn( checker.requestAvatarId(UsernamePassword(b"anybody", b"secret")), b"anybody" ) def test_verifyPassword(self): """ If the encrypted password provided by the getpwnam function is valid (verified by the L{verifyCryptedPassword} function), we callback the C{requestAvatarId} L{Deferred} with the username. """ def verifyCryptedPassword(crypted, pw): return crypted == pw def getpwnam(username): return [username, username] self.patch(checkers, "verifyCryptedPassword", verifyCryptedPassword) checker = checkers.UNIXPasswordDatabase([getpwnam]) credential = UsernamePassword(b"username", b"username") self.assertLoggedIn(checker.requestAvatarId(credential), b"username") def test_failOnKeyError(self): """ If the getpwnam function raises a KeyError, the login fails with an L{UnauthorizedLogin} exception. """ def getpwnam(username): raise KeyError(username) checker = checkers.UNIXPasswordDatabase([getpwnam]) credential = UsernamePassword(b"username", b"password") self.assertUnauthorizedLogin(checker.requestAvatarId(credential)) def test_failOnBadPassword(self): """ If the verifyCryptedPassword function doesn't verify the password, the login fails with an L{UnauthorizedLogin} exception. """ def verifyCryptedPassword(crypted, pw): return False def getpwnam(username): return [username, b"password"] self.patch(checkers, "verifyCryptedPassword", verifyCryptedPassword) checker = checkers.UNIXPasswordDatabase([getpwnam]) credential = UsernamePassword(b"username", b"password") self.assertUnauthorizedLogin(checker.requestAvatarId(credential)) def test_loopThroughFunctions(self): """ UNIXPasswordDatabase.requestAvatarId loops through each getpwnam function associated with it and returns a L{Deferred} which fires with the result of the first one which returns a value other than None. ones do not verify the password. """ def verifyCryptedPassword(crypted, pw): return crypted == pw def getpwnam1(username): return [username, "not the password"] def getpwnam2(username): return [username, "password"] self.patch(checkers, "verifyCryptedPassword", verifyCryptedPassword) checker = checkers.UNIXPasswordDatabase([getpwnam1, getpwnam2]) credential = UsernamePassword(b"username", b"password") self.assertLoggedIn(checker.requestAvatarId(credential), b"username") def test_failOnSpecial(self): """ If the password returned by any function is C{""}, C{"x"}, or C{"*"} it is not compared against the supplied password. Instead it is skipped. """ pwd = UserDatabase() pwd.addUser("alice", "", 1, 2, "", "foo", "bar") pwd.addUser("bob", "x", 1, 2, "", "foo", "bar") pwd.addUser("carol", "*", 1, 2, "", "foo", "bar") self.patch(checkers, "pwd", pwd) checker = checkers.UNIXPasswordDatabase([checkers._pwdGetByName]) cred = UsernamePassword(b"alice", b"") self.assertUnauthorizedLogin(checker.requestAvatarId(cred)) cred = UsernamePassword(b"bob", b"x") self.assertUnauthorizedLogin(checker.requestAvatarId(cred)) cred = UsernamePassword(b"carol", b"*") self.assertUnauthorizedLogin(checker.requestAvatarId(cred)) class AuthorizedKeyFileReaderTests(TestCase): """ Tests for L{checkers.readAuthorizedKeyFile} """ skip = dependencySkip def test_ignoresComments(self): """ L{checkers.readAuthorizedKeyFile} does not attempt to turn comments into keys """ fileobj = BytesIO( b"# this comment is ignored\n" b"this is not\n" b"# this is again\n" b"and this is not" ) result = checkers.readAuthorizedKeyFile(fileobj, lambda x: x) self.assertEqual([b"this is not", b"and this is not"], list(result)) def test_ignoresLeadingWhitespaceAndEmptyLines(self): """ L{checkers.readAuthorizedKeyFile} ignores leading whitespace in lines, as well as empty lines """ fileobj = BytesIO( b""" # ignore not ignored """ ) result = checkers.readAuthorizedKeyFile(fileobj, parseKey=lambda x: x) self.assertEqual([b"not ignored"], list(result)) def test_ignoresUnparsableKeys(self): """ L{checkers.readAuthorizedKeyFile} does not raise an exception when a key fails to parse (raises a L{twisted.conch.ssh.keys.BadKeyError}), but rather just keeps going """ def failOnSome(line): if line.startswith(b"f"): raise keys.BadKeyError("failed to parse") return line fileobj = BytesIO(b"failed key\ngood key") result = checkers.readAuthorizedKeyFile(fileobj, parseKey=failOnSome) self.assertEqual([b"good key"], list(result)) class InMemorySSHKeyDBTests(TestCase): """ Tests for L{checkers.InMemorySSHKeyDB} """ skip = dependencySkip def test_implementsInterface(self): """ L{checkers.InMemorySSHKeyDB} implements L{checkers.IAuthorizedKeysDB} """ keydb = checkers.InMemorySSHKeyDB({b"alice": [b"key"]}) verifyObject(checkers.IAuthorizedKeysDB, keydb) def test_noKeysForUnauthorizedUser(self): """ If the user is not in the mapping provided to L{checkers.InMemorySSHKeyDB}, an empty iterator is returned by L{checkers.InMemorySSHKeyDB.getAuthorizedKeys} """ keydb = checkers.InMemorySSHKeyDB({b"alice": [b"keys"]}) self.assertEqual([], list(keydb.getAuthorizedKeys(b"bob"))) def test_allKeysForAuthorizedUser(self): """ If the user is in the mapping provided to L{checkers.InMemorySSHKeyDB}, an iterator with all the keys is returned by L{checkers.InMemorySSHKeyDB.getAuthorizedKeys} """ keydb = checkers.InMemorySSHKeyDB({b"alice": [b"a", b"b"]}) self.assertEqual([b"a", b"b"], list(keydb.getAuthorizedKeys(b"alice"))) class UNIXAuthorizedKeysFilesTests(TestCase): """ Tests for L{checkers.UNIXAuthorizedKeysFiles}. """ skip = dependencySkip def setUp(self) -> None: self.path = FilePath(self.mktemp()) assert isinstance(self.path.path, str) self.path.makedirs() self.userdb = UserDatabase() self.userdb.addUser( "alice", "password", 1, 2, "alice lastname", self.path.path, "/bin/shell", ) self.sshDir = self.path.child(".ssh") self.sshDir.makedirs() authorizedKeys = self.sshDir.child("authorized_keys") authorizedKeys.setContent(b"key 1\nkey 2") self.expectedKeys = [b"key 1", b"key 2"] def test_implementsInterface(self): """ L{checkers.UNIXAuthorizedKeysFiles} implements L{checkers.IAuthorizedKeysDB}. """ keydb = checkers.UNIXAuthorizedKeysFiles(self.userdb) verifyObject(checkers.IAuthorizedKeysDB, keydb) def test_noKeysForUnauthorizedUser(self): """ If the user is not in the user database provided to L{checkers.UNIXAuthorizedKeysFiles}, an empty iterator is returned by L{checkers.UNIXAuthorizedKeysFiles.getAuthorizedKeys}. """ keydb = checkers.UNIXAuthorizedKeysFiles(self.userdb, parseKey=lambda x: x) self.assertEqual([], list(keydb.getAuthorizedKeys(b"bob"))) def test_allKeysInAllAuthorizedFilesForAuthorizedUser(self): """ If the user is in the user database provided to L{checkers.UNIXAuthorizedKeysFiles}, an iterator with all the keys in C{~/.ssh/authorized_keys} and C{~/.ssh/authorized_keys2} is returned by L{checkers.UNIXAuthorizedKeysFiles.getAuthorizedKeys}. """ self.sshDir.child("authorized_keys2").setContent(b"key 3") keydb = checkers.UNIXAuthorizedKeysFiles(self.userdb, parseKey=lambda x: x) self.assertEqual( self.expectedKeys + [b"key 3"], list(keydb.getAuthorizedKeys(b"alice")) ) def test_ignoresNonexistantFile(self): """ L{checkers.UNIXAuthorizedKeysFiles.getAuthorizedKeys} returns only the keys in C{~/.ssh/authorized_keys} and C{~/.ssh/authorized_keys2} if they exist. """ keydb = checkers.UNIXAuthorizedKeysFiles(self.userdb, parseKey=lambda x: x) self.assertEqual(self.expectedKeys, list(keydb.getAuthorizedKeys(b"alice"))) def test_ignoresUnreadableFile(self): """ L{checkers.UNIXAuthorizedKeysFiles.getAuthorizedKeys} returns only the keys in C{~/.ssh/authorized_keys} and C{~/.ssh/authorized_keys2} if they are readable. """ self.sshDir.child("authorized_keys2").makedirs() keydb = checkers.UNIXAuthorizedKeysFiles(self.userdb, parseKey=lambda x: x) self.assertEqual(self.expectedKeys, list(keydb.getAuthorizedKeys(b"alice"))) _KeyDB = namedtuple("_KeyDB", ["getAuthorizedKeys"]) class _DummyException(Exception): """ Fake exception to be used for testing. """ pass class SSHPublicKeyCheckerTests(TestCase): """ Tests for L{checkers.SSHPublicKeyChecker}. """ skip = dependencySkip def setUp(self): self.credentials = SSHPrivateKey( b"alice", b"ssh-rsa", keydata.publicRSA_openssh, b"foo", keys.Key.fromString(keydata.privateRSA_openssh).sign(b"foo"), ) self.keydb = _KeyDB(lambda _: [keys.Key.fromString(keydata.publicRSA_openssh)]) self.checker = checkers.SSHPublicKeyChecker(self.keydb) def test_credentialsWithoutSignature(self): """ Calling L{checkers.SSHPublicKeyChecker.requestAvatarId} with credentials that do not have a signature fails with L{ValidPublicKey}. """ self.credentials.signature = None self.failureResultOf( self.checker.requestAvatarId(self.credentials), ValidPublicKey ) def test_credentialsWithBadKey(self): """ Calling L{checkers.SSHPublicKeyChecker.requestAvatarId} with credentials that have a bad key fails with L{keys.BadKeyError}. """ self.credentials.blob = b"" self.failureResultOf( self.checker.requestAvatarId(self.credentials), keys.BadKeyError ) def test_credentialsNoMatchingKey(self): """ If L{checkers.IAuthorizedKeysDB.getAuthorizedKeys} returns no keys that match the credentials, L{checkers.SSHPublicKeyChecker.requestAvatarId} fails with L{UnauthorizedLogin}. """ self.credentials.blob = keydata.publicDSA_openssh self.failureResultOf( self.checker.requestAvatarId(self.credentials), UnauthorizedLogin ) def test_credentialsInvalidSignature(self): """ Calling L{checkers.SSHPublicKeyChecker.requestAvatarId} with credentials that are incorrectly signed fails with L{UnauthorizedLogin}. """ self.credentials.signature = keys.Key.fromString( keydata.privateDSA_openssh ).sign(b"foo") self.failureResultOf( self.checker.requestAvatarId(self.credentials), UnauthorizedLogin ) def test_failureVerifyingKey(self): """ If L{keys.Key.verify} raises an exception, L{checkers.SSHPublicKeyChecker.requestAvatarId} fails with L{UnauthorizedLogin}. """ def fail(*args, **kwargs): raise _DummyException() self.patch(keys.Key, "verify", fail) self.failureResultOf( self.checker.requestAvatarId(self.credentials), UnauthorizedLogin ) self.flushLoggedErrors(_DummyException) def test_usernameReturnedOnSuccess(self): """ L{checker.SSHPublicKeyChecker.requestAvatarId}, if successful, callbacks with the username. """ d = self.checker.requestAvatarId(self.credentials) self.assertEqual(b"alice", self.successResultOf(d))