diff options
Diffstat (limited to 'lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/conch/test/test_knownhosts.py')
-rwxr-xr-x | lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/conch/test/test_knownhosts.py | 1037 |
1 files changed, 0 insertions, 1037 deletions
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/conch/test/test_knownhosts.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/conch/test/test_knownhosts.py deleted file mode 100755 index d7fdacf0..00000000 --- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/conch/test/test_knownhosts.py +++ /dev/null @@ -1,1037 +0,0 @@ -# Copyright (c) Twisted Matrix Laboratories. -# See LICENSE for details. - -""" -Tests for L{twisted.conch.client.knownhosts}. -""" - -import os -from binascii import Error as BinasciiError, b2a_base64, a2b_base64 - -try: - import Crypto - import pyasn1 -except ImportError: - skip = "PyCrypto and PyASN1 required for twisted.conch.knownhosts." -else: - from twisted.conch.ssh.keys import Key, BadKeyError - from twisted.conch.client.knownhosts import \ - PlainEntry, HashedEntry, KnownHostsFile, UnparsedEntry, ConsoleUI - from twisted.conch.client import default - -from zope.interface.verify import verifyObject - -from twisted.python.filepath import FilePath -from twisted.trial.unittest import TestCase -from twisted.internet.defer import Deferred -from twisted.conch.interfaces import IKnownHostEntry -from twisted.conch.error import HostKeyChanged, UserRejectedKey, InvalidEntry - - -sampleEncodedKey = ( - 'AAAAB3NzaC1yc2EAAAABIwAAAQEAsV0VMRbGmzhqxxayLRHmvnFvtyNqgbNKV46dU1bVFB+3y' - 'tNvue4Riqv/SVkPRNwMb7eWH29SviXaBxUhYyzKkDoNUq3rTNnH1Vnif6d6X4JCrUb5d3W+Dm' - 'YClyJrZ5HgD/hUpdSkTRqdbQ2TrvSAxRacj+vHHT4F4dm1bJSewm3B2D8HVOoi/CbVh3dsIiC' - 'dp8VltdZx4qYVfYe2LwVINCbAa3d3tj9ma7RVfw3OH2Mfb+toLd1N5tBQFb7oqTt2nC6I/6Bd' - '4JwPUld+IEitw/suElq/AIJVQXXujeyiZlea90HE65U2mF1ytr17HTAIT2ySokJWyuBANGACk' - '6iIaw==') - -otherSampleEncodedKey = ( - 'AAAAB3NzaC1yc2EAAAABIwAAAIEAwaeCZd3UCuPXhX39+/p9qO028jTF76DMVd9mPvYVDVXuf' - 'WckKZauF7+0b7qm+ChT7kan6BzRVo4++gCVNfAlMzLysSt3ylmOR48tFpAfygg9UCX3DjHz0E' - 'lOOUKh3iifc9aUShD0OPaK3pR5JJ8jfiBfzSYWt/hDi/iZ4igsSs8=') - -thirdSampleEncodedKey = ( - 'AAAAB3NzaC1yc2EAAAABIwAAAQEAl/TQakPkePlnwCBRPitIVUTg6Z8VzN1en+DGkyo/evkmLw' - '7o4NWR5qbysk9A9jXW332nxnEuAnbcCam9SHe1su1liVfyIK0+3bdn0YRB0sXIbNEtMs2LtCho' - '/aV3cXPS+Cf1yut3wvIpaRnAzXxuKPCTXQ7/y0IXa8TwkRBH58OJa3RqfQ/NsSp5SAfdsrHyH2' - 'aitiVKm2jfbTKzSEqOQG/zq4J9GXTkq61gZugory/Tvl5/yPgSnOR6C9jVOMHf27ZPoRtyj9SY' - '343Hd2QHiIE0KPZJEgCynKeWoKz8v6eTSK8n4rBnaqWdp8MnGZK1WGy05MguXbyCDuTC8AmJXQ' - '==') - -sampleKey = a2b_base64(sampleEncodedKey) -otherSampleKey = a2b_base64(otherSampleEncodedKey) -thirdSampleKey = a2b_base64(thirdSampleEncodedKey) - -samplePlaintextLine = ( - "www.twistedmatrix.com ssh-rsa " + sampleEncodedKey + "\n") - -otherSamplePlaintextLine = ( - "divmod.com ssh-rsa " + otherSampleEncodedKey + "\n") - -sampleHostIPLine = ( - "www.twistedmatrix.com,198.49.126.131 ssh-rsa " + sampleEncodedKey + "\n") - -sampleHashedLine = ( - "|1|gJbSEPBG9ZSBoZpHNtZBD1bHKBA=|bQv+0Xa0dByrwkA1EB0E7Xop/Fo= ssh-rsa " + - sampleEncodedKey + "\n") - - - -class EntryTestsMixin: - """ - Tests for implementations of L{IKnownHostEntry}. Subclasses must set the - 'entry' attribute to a provider of that interface, the implementation of - that interface under test. - - @ivar entry: a provider of L{IKnownHostEntry} with a hostname of - www.twistedmatrix.com and an RSA key of sampleKey. - """ - - def test_providesInterface(self): - """ - The given entry should provide IKnownHostEntry. - """ - verifyObject(IKnownHostEntry, self.entry) - - - def test_fromString(self): - """ - Constructing a plain text entry from an unhashed known_hosts entry will - result in an L{IKnownHostEntry} provider with 'keyString', 'hostname', - and 'keyType' attributes. While outside the interface in question, - these attributes are held in common by L{PlainEntry} and L{HashedEntry} - implementations; other implementations should override this method in - subclasses. - """ - entry = self.entry - self.assertEqual(entry.publicKey, Key.fromString(sampleKey)) - self.assertEqual(entry.keyType, "ssh-rsa") - - - def test_matchesKey(self): - """ - L{IKnownHostEntry.matchesKey} checks to see if an entry matches a given - SSH key. - """ - twistedmatrixDotCom = Key.fromString(sampleKey) - divmodDotCom = Key.fromString(otherSampleKey) - self.assertEqual( - True, - self.entry.matchesKey(twistedmatrixDotCom)) - self.assertEqual( - False, - self.entry.matchesKey(divmodDotCom)) - - - def test_matchesHost(self): - """ - L{IKnownHostEntry.matchesHost} checks to see if an entry matches a - given hostname. - """ - self.assertEqual(True, self.entry.matchesHost( - "www.twistedmatrix.com")) - self.assertEqual(False, self.entry.matchesHost( - "www.divmod.com")) - - - -class PlainEntryTests(EntryTestsMixin, TestCase): - """ - Test cases for L{PlainEntry}. - """ - plaintextLine = samplePlaintextLine - hostIPLine = sampleHostIPLine - - def setUp(self): - """ - Set 'entry' to a sample plain-text entry with sampleKey as its key. - """ - self.entry = PlainEntry.fromString(self.plaintextLine) - - - def test_matchesHostIP(self): - """ - A "hostname,ip" formatted line will match both the host and the IP. - """ - self.entry = PlainEntry.fromString(self.hostIPLine) - self.assertEqual(True, self.entry.matchesHost("198.49.126.131")) - self.test_matchesHost() - - - def test_toString(self): - """ - L{PlainEntry.toString} generates the serialized OpenSSL format string - for the entry, sans newline. - """ - self.assertEqual(self.entry.toString(), self.plaintextLine.rstrip("\n")) - multiHostEntry = PlainEntry.fromString(self.hostIPLine) - self.assertEqual(multiHostEntry.toString(), - self.hostIPLine.rstrip("\n")) - - - -class PlainTextWithCommentTests(PlainEntryTests): - """ - Test cases for L{PlainEntry} when parsed from a line with a comment. - """ - - plaintextLine = samplePlaintextLine[:-1] + " plain text comment.\n" - hostIPLine = sampleHostIPLine[:-1] + " text following host/IP line\n" - - - -class HashedEntryTests(EntryTestsMixin, TestCase): - """ - Tests for L{HashedEntry}. - - This suite doesn't include any tests for host/IP pairs because hashed - entries store IP addresses the same way as hostnames and does not support - comma-separated lists. (If you hash the IP and host together you can't - tell if you've got the key already for one or the other.) - """ - hashedLine = sampleHashedLine - - def setUp(self): - """ - Set 'entry' to a sample hashed entry for twistedmatrix.com with - sampleKey as its key. - """ - self.entry = HashedEntry.fromString(self.hashedLine) - - - def test_toString(self): - """ - L{HashedEntry.toString} generates the serialized OpenSSL format string - for the entry, sans the newline. - """ - self.assertEqual(self.entry.toString(), self.hashedLine.rstrip("\n")) - - - -class HashedEntryWithCommentTests(HashedEntryTests): - """ - Test cases for L{PlainEntry} when parsed from a line with a comment. - """ - - hashedLine = sampleHashedLine[:-1] + " plain text comment.\n" - - - -class UnparsedEntryTests(TestCase, EntryTestsMixin): - """ - Tests for L{UnparsedEntry} - """ - def setUp(self): - """ - Set up the 'entry' to be an unparsed entry for some random text. - """ - self.entry = UnparsedEntry(" This is a bogus entry. \n") - - - def test_fromString(self): - """ - Creating an L{UnparsedEntry} should simply record the string it was - passed. - """ - self.assertEqual(" This is a bogus entry. \n", - self.entry._string) - - - def test_matchesHost(self): - """ - An unparsed entry can't match any hosts. - """ - self.assertEqual(False, self.entry.matchesHost("www.twistedmatrix.com")) - - - def test_matchesKey(self): - """ - An unparsed entry can't match any keys. - """ - self.assertEqual(False, self.entry.matchesKey(Key.fromString(sampleKey))) - - - def test_toString(self): - """ - L{UnparsedEntry.toString} returns its input string, sans trailing - newline. - """ - self.assertEqual(" This is a bogus entry. ", self.entry.toString()) - - - -class ParseErrorTests(TestCase): - """ - L{HashedEntry.fromString} and L{PlainEntry.fromString} can raise a variety - of errors depending on misformattings of certain strings. These tests make - sure those errors are caught. Since many of the ways that this can go - wrong are in the lower-level APIs being invoked by the parsing logic, - several of these are integration tests with the C{base64} and - L{twisted.conch.ssh.keys} modules. - """ - - def invalidEntryTest(self, cls): - """ - If there are fewer than three elements, C{fromString} should raise - L{InvalidEntry}. - """ - self.assertRaises(InvalidEntry, cls.fromString, "invalid") - - - def notBase64Test(self, cls): - """ - If the key is not base64, C{fromString} should raise L{BinasciiError}. - """ - self.assertRaises(BinasciiError, cls.fromString, "x x x") - - - def badKeyTest(self, cls, prefix): - """ - If the key portion of the entry is valid base64, but is not actually an - SSH key, C{fromString} should raise L{BadKeyError}. - """ - self.assertRaises(BadKeyError, cls.fromString, ' '.join( - [prefix, "ssh-rsa", b2a_base64( - "Hey, this isn't an SSH key!").strip()])) - - - def test_invalidPlainEntry(self): - """ - If there are fewer than three whitespace-separated elements in an - entry, L{PlainEntry.fromString} should raise L{InvalidEntry}. - """ - self.invalidEntryTest(PlainEntry) - - - def test_invalidHashedEntry(self): - """ - If there are fewer than three whitespace-separated elements in an - entry, or the hostname salt/hash portion has more than two elements, - L{HashedEntry.fromString} should raise L{InvalidEntry}. - """ - self.invalidEntryTest(HashedEntry) - a, b, c = sampleHashedLine.split() - self.assertRaises(InvalidEntry, HashedEntry.fromString, ' '.join( - [a + "||", b, c])) - - - def test_plainNotBase64(self): - """ - If the key portion of a plain entry is not decodable as base64, - C{fromString} should raise L{BinasciiError}. - """ - self.notBase64Test(PlainEntry) - - - def test_hashedNotBase64(self): - """ - If the key, host salt, or host hash portion of a hashed entry is not - encoded, it will raise L{BinasciiError}. - """ - self.notBase64Test(HashedEntry) - a, b, c = sampleHashedLine.split() - # Salt not valid base64. - self.assertRaises( - BinasciiError, HashedEntry.fromString, - ' '.join(["|1|x|" + b2a_base64("stuff").strip(), b, c])) - # Host hash not valid base64. - self.assertRaises( - BinasciiError, HashedEntry.fromString, - ' '.join([HashedEntry.MAGIC + b2a_base64("stuff").strip() + "|x", - b, c])) - # Neither salt nor hash valid base64. - self.assertRaises( - BinasciiError, HashedEntry.fromString, - ' '.join(["|1|x|x", b, c])) - - - def test_hashedBadKey(self): - """ - If the key portion of the entry is valid base64, but is not actually an - SSH key, C{HashedEntry.fromString} should raise L{BadKeyError}. - """ - a, b, c = sampleHashedLine.split() - self.badKeyTest(HashedEntry, a) - - - def test_plainBadKey(self): - """ - If the key portion of the entry is valid base64, but is not actually an - SSH key, C{PlainEntry.fromString} should raise L{BadKeyError}. - """ - self.badKeyTest(PlainEntry, "hostname") - - - -class KnownHostsDatabaseTests(TestCase): - """ - Tests for L{KnownHostsFile}. - """ - - def pathWithContent(self, content): - """ - Return a FilePath with the given initial content. - """ - fp = FilePath(self.mktemp()) - fp.setContent(content) - return fp - - - def loadSampleHostsFile(self, content=( - sampleHashedLine + otherSamplePlaintextLine + - "\n# That was a blank line.\n" - "This is just unparseable.\n" - "|1|This also unparseable.\n")): - """ - Return a sample hosts file, with keys for www.twistedmatrix.com and - divmod.com present. - """ - return KnownHostsFile.fromPath(self.pathWithContent(content)) - - - def test_loadFromPath(self): - """ - Loading a L{KnownHostsFile} from a path with six entries in it will - result in a L{KnownHostsFile} object with six L{IKnownHostEntry} - providers in it. - """ - hostsFile = self.loadSampleHostsFile() - self.assertEqual(len(hostsFile._entries), 6) - - - def test_verifyHashedEntry(self): - """ - Loading a L{KnownHostsFile} from a path containing a single valid - L{HashedEntry} entry will result in a L{KnownHostsFile} object - with one L{IKnownHostEntry} provider. - """ - hostsFile = self.loadSampleHostsFile((sampleHashedLine)) - self.assertIsInstance(hostsFile._entries[0], HashedEntry) - self.assertEqual(True, hostsFile._entries[0].matchesHost( - "www.twistedmatrix.com")) - - - def test_verifyPlainEntry(self): - """ - Loading a L{KnownHostsFile} from a path containing a single valid - L{PlainEntry} entry will result in a L{KnownHostsFile} object - with one L{IKnownHostEntry} provider. - """ - hostsFile = self.loadSampleHostsFile((otherSamplePlaintextLine)) - self.assertIsInstance(hostsFile._entries[0], PlainEntry) - self.assertEqual(True, hostsFile._entries[0].matchesHost( - "divmod.com")) - - - def test_verifyUnparsedEntry(self): - """ - Loading a L{KnownHostsFile} from a path that only contains '\n' will - result in a L{KnownHostsFile} object containing a L{UnparsedEntry} - object. - """ - hostsFile = self.loadSampleHostsFile(("\n")) - self.assertIsInstance(hostsFile._entries[0], UnparsedEntry) - self.assertEqual(hostsFile._entries[0].toString(), "") - - - def test_verifyUnparsedComment(self): - """ - Loading a L{KnownHostsFile} from a path that contains a comment will - result in a L{KnownHostsFile} object containing a L{UnparsedEntry} - object. - """ - hostsFile = self.loadSampleHostsFile(("# That was a blank line.\n")) - self.assertIsInstance(hostsFile._entries[0], UnparsedEntry) - self.assertEqual(hostsFile._entries[0].toString(), - "# That was a blank line.") - - - def test_verifyUnparsableLine(self): - """ - Loading a L{KnownHostsFile} from a path that contains an unparseable - line will be represented as an L{UnparsedEntry} instance. - """ - hostsFile = self.loadSampleHostsFile(("This is just unparseable.\n")) - self.assertIsInstance(hostsFile._entries[0], UnparsedEntry) - self.assertEqual(hostsFile._entries[0].toString(), - "This is just unparseable.") - - - def test_verifyUnparsableEncryptionMarker(self): - """ - Loading a L{KnownHostsFile} from a path containing an unparseable line - that starts with an encryption marker will be represented as an - L{UnparsedEntry} instance. - """ - hostsFile = self.loadSampleHostsFile(("|1|This is unparseable.\n")) - self.assertIsInstance(hostsFile._entries[0], UnparsedEntry) - self.assertEqual(hostsFile._entries[0].toString(), - "|1|This is unparseable.") - - - def test_loadNonExistent(self): - """ - Loading a L{KnownHostsFile} from a path that does not exist should - result in an empty L{KnownHostsFile} that will save back to that path. - """ - pn = self.mktemp() - knownHostsFile = KnownHostsFile.fromPath(FilePath(pn)) - self.assertEqual([], list(knownHostsFile._entries)) - self.assertEqual(False, FilePath(pn).exists()) - knownHostsFile.save() - self.assertEqual(True, FilePath(pn).exists()) - - - def test_loadNonExistentParent(self): - """ - Loading a L{KnownHostsFile} from a path whose parent directory does not - exist should result in an empty L{KnownHostsFile} that will save back - to that path, creating its parent directory(ies) in the process. - """ - thePath = FilePath(self.mktemp()) - knownHostsPath = thePath.child("foo").child("known_hosts") - knownHostsFile = KnownHostsFile.fromPath(knownHostsPath) - knownHostsFile.save() - knownHostsPath.restat(False) - self.assertEqual(True, knownHostsPath.exists()) - - - def test_savingAddsEntry(self): - """ - L{KnownHostsFile.save()} will write out a new file with any entries - that have been added. - """ - path = self.pathWithContent(sampleHashedLine + - otherSamplePlaintextLine) - knownHostsFile = KnownHostsFile.fromPath(path) - newEntry = knownHostsFile.addHostKey("some.example.com", - Key.fromString(thirdSampleKey)) - expectedContent = ( - sampleHashedLine + - otherSamplePlaintextLine + HashedEntry.MAGIC + - b2a_base64(newEntry._hostSalt).strip() + "|" + - b2a_base64(newEntry._hostHash).strip() + " ssh-rsa " + - thirdSampleEncodedKey + "\n") - - # Sanity check, let's make sure the base64 API being used for the test - # isn't inserting spurious newlines. - self.assertEqual(3, expectedContent.count("\n")) - knownHostsFile.save() - self.assertEqual(expectedContent, path.getContent()) - - - def test_hasPresentKey(self): - """ - L{KnownHostsFile.hasHostKey} returns C{True} when a key for the given - hostname is present and matches the expected key. - """ - hostsFile = self.loadSampleHostsFile() - self.assertEqual(True, hostsFile.hasHostKey( - "www.twistedmatrix.com", Key.fromString(sampleKey))) - - - def test_hasNonPresentKey(self): - """ - L{KnownHostsFile.hasHostKey} returns C{False} when a key for the given - hostname is not present. - """ - hostsFile = self.loadSampleHostsFile() - self.assertEqual(False, hostsFile.hasHostKey( - "non-existent.example.com", Key.fromString(sampleKey))) - - - def test_hasKeyMismatch(self): - """ - L{KnownHostsFile.hasHostKey} raises L{HostKeyChanged} if the host key - is present, but different from the expected one. The resulting - exception should have an C{offendingEntry} indicating the given entry. - """ - hostsFile = self.loadSampleHostsFile() - exception = self.assertRaises( - HostKeyChanged, hostsFile.hasHostKey, - "www.twistedmatrix.com", Key.fromString(otherSampleKey)) - self.assertEqual(exception.offendingEntry, hostsFile._entries[0]) - self.assertEqual(exception.lineno, 1) - self.assertEqual(exception.path, hostsFile._savePath) - - - def test_addHostKey(self): - """ - L{KnownHostsFile.addHostKey} adds a new L{HashedEntry} to the host - file, and returns it. - """ - hostsFile = self.loadSampleHostsFile() - aKey = Key.fromString(thirdSampleKey) - self.assertEqual(False, - hostsFile.hasHostKey("somewhere.example.com", aKey)) - newEntry = hostsFile.addHostKey("somewhere.example.com", aKey) - - # The code in OpenSSH requires host salts to be 20 characters long. - # This is the required length of a SHA-1 HMAC hash, so it's just a - # sanity check. - self.assertEqual(20, len(newEntry._hostSalt)) - self.assertEqual(True, - newEntry.matchesHost("somewhere.example.com")) - self.assertEqual(newEntry.keyType, "ssh-rsa") - self.assertEqual(aKey, newEntry.publicKey) - self.assertEqual(True, - hostsFile.hasHostKey("somewhere.example.com", aKey)) - - - def test_randomSalts(self): - """ - L{KnownHostsFile.addHostKey} generates a random salt for each new key, - so subsequent salts will be different. - """ - hostsFile = self.loadSampleHostsFile() - aKey = Key.fromString(thirdSampleKey) - self.assertNotEqual( - hostsFile.addHostKey("somewhere.example.com", aKey)._hostSalt, - hostsFile.addHostKey("somewhere-else.example.com", aKey)._hostSalt) - - - def test_verifyValidKey(self): - """ - Verifying a valid key should return a L{Deferred} which fires with - True. - """ - hostsFile = self.loadSampleHostsFile() - hostsFile.addHostKey("1.2.3.4", Key.fromString(sampleKey)) - ui = FakeUI() - d = hostsFile.verifyHostKey(ui, "www.twistedmatrix.com", "1.2.3.4", - Key.fromString(sampleKey)) - l = [] - d.addCallback(l.append) - self.assertEqual(l, [True]) - - - def test_verifyInvalidKey(self): - """ - Verfying an invalid key should return a L{Deferred} which fires with a - L{HostKeyChanged} failure. - """ - hostsFile = self.loadSampleHostsFile() - wrongKey = Key.fromString(thirdSampleKey) - ui = FakeUI() - hostsFile.addHostKey("1.2.3.4", Key.fromString(sampleKey)) - d = hostsFile.verifyHostKey( - ui, "www.twistedmatrix.com", "1.2.3.4", wrongKey) - return self.assertFailure(d, HostKeyChanged) - - - def verifyNonPresentKey(self): - """ - Set up a test to verify a key that isn't present. Return a 3-tuple of - the UI, a list set up to collect the result of the verifyHostKey call, - and the sample L{KnownHostsFile} being used. - - This utility method avoids returning a L{Deferred}, and records results - in the returned list instead, because the events which get generated - here are pre-recorded in the 'ui' object. If the L{Deferred} in - question does not fire, the it will fail quickly with an empty list. - """ - hostsFile = self.loadSampleHostsFile() - absentKey = Key.fromString(thirdSampleKey) - ui = FakeUI() - l = [] - d = hostsFile.verifyHostKey( - ui, "sample-host.example.com", "4.3.2.1", absentKey) - d.addBoth(l.append) - self.assertEqual([], l) - self.assertEqual( - ui.promptText, - "The authenticity of host 'sample-host.example.com (4.3.2.1)' " - "can't be established.\n" - "RSA key fingerprint is " - "89:4e:cc:8c:57:83:96:48:ef:63:ad:ee:99:00:4c:8f.\n" - "Are you sure you want to continue connecting (yes/no)? ") - return ui, l, hostsFile - - - def test_verifyNonPresentKey_Yes(self): - """ - Verifying a key where neither the hostname nor the IP are present - should result in the UI being prompted with a message explaining as - much. If the UI says yes, the Deferred should fire with True. - """ - ui, l, knownHostsFile = self.verifyNonPresentKey() - ui.promptDeferred.callback(True) - self.assertEqual([True], l) - reloaded = KnownHostsFile.fromPath(knownHostsFile._savePath) - self.assertEqual( - True, - reloaded.hasHostKey("4.3.2.1", Key.fromString(thirdSampleKey))) - self.assertEqual( - True, - reloaded.hasHostKey("sample-host.example.com", - Key.fromString(thirdSampleKey))) - - - def test_verifyNonPresentKey_No(self): - """ - Verifying a key where neither the hostname nor the IP are present - should result in the UI being prompted with a message explaining as - much. If the UI says no, the Deferred should fail with - UserRejectedKey. - """ - ui, l, knownHostsFile = self.verifyNonPresentKey() - ui.promptDeferred.callback(False) - l[0].trap(UserRejectedKey) - - - def test_verifyHostIPMismatch(self): - """ - Verifying a key where the host is present (and correct), but the IP is - present and different, should result the deferred firing in a - HostKeyChanged failure. - """ - hostsFile = self.loadSampleHostsFile() - wrongKey = Key.fromString(thirdSampleKey) - ui = FakeUI() - d = hostsFile.verifyHostKey( - ui, "www.twistedmatrix.com", "4.3.2.1", wrongKey) - return self.assertFailure(d, HostKeyChanged) - - - def test_verifyKeyForHostAndIP(self): - """ - Verifying a key where the hostname is present but the IP is not should - result in the key being added for the IP and the user being warned - about the change. - """ - ui = FakeUI() - hostsFile = self.loadSampleHostsFile() - expectedKey = Key.fromString(sampleKey) - hostsFile.verifyHostKey( - ui, "www.twistedmatrix.com", "5.4.3.2", expectedKey) - self.assertEqual( - True, KnownHostsFile.fromPath(hostsFile._savePath).hasHostKey( - "5.4.3.2", expectedKey)) - self.assertEqual( - ["Warning: Permanently added the RSA host key for IP address " - "'5.4.3.2' to the list of known hosts."], - ui.userWarnings) - - -class FakeFile(object): - """ - A fake file-like object that acts enough like a file for - L{ConsoleUI.prompt}. - """ - - def __init__(self): - self.inlines = [] - self.outchunks = [] - self.closed = False - - - def readline(self): - """ - Return a line from the 'inlines' list. - """ - return self.inlines.pop(0) - - - def write(self, chunk): - """ - Append the given item to the 'outchunks' list. - """ - if self.closed: - raise IOError("the file was closed") - self.outchunks.append(chunk) - - - def close(self): - """ - Set the 'closed' flag to True, explicitly marking that it has been - closed. - """ - self.closed = True - - - -class ConsoleUITests(TestCase): - """ - Test cases for L{ConsoleUI}. - """ - - def setUp(self): - """ - Create a L{ConsoleUI} pointed at a L{FakeFile}. - """ - self.fakeFile = FakeFile() - self.ui = ConsoleUI(self.openFile) - - - def openFile(self): - """ - Return the current fake file. - """ - return self.fakeFile - - - def newFile(self, lines): - """ - Create a new fake file (the next file that self.ui will open) with the - given list of lines to be returned from readline(). - """ - self.fakeFile = FakeFile() - self.fakeFile.inlines = lines - - - def test_promptYes(self): - """ - L{ConsoleUI.prompt} writes a message to the console, then reads a line. - If that line is 'yes', then it returns a L{Deferred} that fires with - True. - """ - for okYes in ['yes', 'Yes', 'yes\n']: - self.newFile([okYes]) - l = [] - self.ui.prompt("Hello, world!").addCallback(l.append) - self.assertEqual(["Hello, world!"], self.fakeFile.outchunks) - self.assertEqual([True], l) - self.assertEqual(True, self.fakeFile.closed) - - - def test_promptNo(self): - """ - L{ConsoleUI.prompt} writes a message to the console, then reads a line. - If that line is 'no', then it returns a L{Deferred} that fires with - False. - """ - for okNo in ['no', 'No', 'no\n']: - self.newFile([okNo]) - l = [] - self.ui.prompt("Goodbye, world!").addCallback(l.append) - self.assertEqual(["Goodbye, world!"], self.fakeFile.outchunks) - self.assertEqual([False], l) - self.assertEqual(True, self.fakeFile.closed) - - - def test_promptRepeatedly(self): - """ - L{ConsoleUI.prompt} writes a message to the console, then reads a line. - If that line is neither 'yes' nor 'no', then it says "Please enter - 'yes' or 'no'" until it gets a 'yes' or a 'no', at which point it - returns a Deferred that answers either True or False. - """ - self.newFile(['what', 'uh', 'okay', 'yes']) - l = [] - self.ui.prompt("Please say something useful.").addCallback(l.append) - self.assertEqual([True], l) - self.assertEqual(self.fakeFile.outchunks, - ["Please say something useful."] + - ["Please type 'yes' or 'no': "] * 3) - self.assertEqual(True, self.fakeFile.closed) - self.newFile(['blah', 'stuff', 'feh', 'no']) - l = [] - self.ui.prompt("Please say something negative.").addCallback(l.append) - self.assertEqual([False], l) - self.assertEqual(self.fakeFile.outchunks, - ["Please say something negative."] + - ["Please type 'yes' or 'no': "] * 3) - self.assertEqual(True, self.fakeFile.closed) - - - def test_promptOpenFailed(self): - """ - If the C{opener} passed to L{ConsoleUI} raises an exception, that - exception will fail the L{Deferred} returned from L{ConsoleUI.prompt}. - """ - def raiseIt(): - raise IOError() - ui = ConsoleUI(raiseIt) - d = ui.prompt("This is a test.") - return self.assertFailure(d, IOError) - - - def test_warn(self): - """ - L{ConsoleUI.warn} should output a message to the console object. - """ - self.ui.warn("Test message.") - self.assertEqual(["Test message."], self.fakeFile.outchunks) - self.assertEqual(True, self.fakeFile.closed) - - - def test_warnOpenFailed(self): - """ - L{ConsoleUI.warn} should log a traceback if the output can't be opened. - """ - def raiseIt(): - 1 / 0 - ui = ConsoleUI(raiseIt) - ui.warn("This message never makes it.") - self.assertEqual(len(self.flushLoggedErrors(ZeroDivisionError)), 1) - - - -class FakeUI(object): - """ - A fake UI object, adhering to the interface expected by - L{KnownHostsFile.verifyHostKey} - - @ivar userWarnings: inputs provided to 'warn'. - - @ivar promptDeferred: last result returned from 'prompt'. - - @ivar promptText: the last input provided to 'prompt'. - """ - - def __init__(self): - self.userWarnings = [] - self.promptDeferred = None - self.promptText = None - - - def prompt(self, text): - """ - Issue the user an interactive prompt, which they can accept or deny. - """ - self.promptText = text - self.promptDeferred = Deferred() - return self.promptDeferred - - - def warn(self, text): - """ - Issue a non-interactive warning to the user. - """ - self.userWarnings.append(text) - - - -class FakeObject(object): - """ - A fake object that can have some attributes. Used to fake - L{SSHClientTransport} and L{SSHClientFactory}. - """ - - -class DefaultAPITests(TestCase): - """ - The API in L{twisted.conch.client.default.verifyHostKey} is the integration - point between the code in the rest of conch and L{KnownHostsFile}. - """ - - def patchedOpen(self, fname, mode): - """ - The patched version of 'open'; this returns a L{FakeFile} that the - instantiated L{ConsoleUI} can use. - """ - self.assertEqual(fname, "/dev/tty") - self.assertEqual(mode, "r+b") - return self.fakeFile - - - def setUp(self): - """ - Patch 'open' in verifyHostKey. - """ - self.fakeFile = FakeFile() - self.patch(default, "_open", self.patchedOpen) - self.hostsOption = self.mktemp() - knownHostsFile = KnownHostsFile(FilePath(self.hostsOption)) - knownHostsFile.addHostKey("exists.example.com", - Key.fromString(sampleKey)) - knownHostsFile.addHostKey("4.3.2.1", Key.fromString(sampleKey)) - knownHostsFile.save() - self.fakeTransport = FakeObject() - self.fakeTransport.factory = FakeObject() - self.options = self.fakeTransport.factory.options = { - 'host': "exists.example.com", - 'known-hosts': self.hostsOption - } - - - def test_verifyOKKey(self): - """ - L{default.verifyHostKey} should return a L{Deferred} which fires with - C{1} when passed a host, IP, and key which already match the - known_hosts file it is supposed to check. - """ - l = [] - default.verifyHostKey(self.fakeTransport, "4.3.2.1", sampleKey, - "I don't care.").addCallback(l.append) - self.assertEqual([1], l) - - - def replaceHome(self, tempHome): - """ - Replace the HOME environment variable until the end of the current - test, with the given new home-directory, so that L{os.path.expanduser} - will yield controllable, predictable results. - - @param tempHome: the pathname to replace the HOME variable with. - - @type tempHome: L{str} - """ - oldHome = os.environ.get('HOME') - def cleanupHome(): - if oldHome is None: - del os.environ['HOME'] - else: - os.environ['HOME'] = oldHome - self.addCleanup(cleanupHome) - os.environ['HOME'] = tempHome - - - def test_noKnownHostsOption(self): - """ - L{default.verifyHostKey} should find your known_hosts file in - ~/.ssh/known_hosts if you don't specify one explicitly on the command - line. - """ - l = [] - tmpdir = self.mktemp() - oldHostsOption = self.hostsOption - hostsNonOption = FilePath(tmpdir).child(".ssh").child("known_hosts") - hostsNonOption.parent().makedirs() - FilePath(oldHostsOption).moveTo(hostsNonOption) - self.replaceHome(tmpdir) - self.options['known-hosts'] = None - default.verifyHostKey(self.fakeTransport, "4.3.2.1", sampleKey, - "I don't care.").addCallback(l.append) - self.assertEqual([1], l) - - - def test_verifyHostButNotIP(self): - """ - L{default.verifyHostKey} should return a L{Deferred} which fires with - C{1} when passed a host which matches with an IP is not present in its - known_hosts file, and should also warn the user that it has added the - IP address. - """ - l = [] - default.verifyHostKey(self.fakeTransport, "8.7.6.5", sampleKey, - "Fingerprint not required.").addCallback(l.append) - self.assertEqual( - ["Warning: Permanently added the RSA host key for IP address " - "'8.7.6.5' to the list of known hosts."], - self.fakeFile.outchunks) - self.assertEqual([1], l) - knownHostsFile = KnownHostsFile.fromPath(FilePath(self.hostsOption)) - self.assertEqual(True, knownHostsFile.hasHostKey("8.7.6.5", - Key.fromString(sampleKey))) - - - def test_verifyQuestion(self): - """ - L{default.verifyHostKey} should return a L{Default} which fires with - C{0} when passed a unknown host that the user refuses to acknowledge. - """ - self.fakeTransport.factory.options['host'] = 'fake.example.com' - self.fakeFile.inlines.append("no") - d = default.verifyHostKey( - self.fakeTransport, "9.8.7.6", otherSampleKey, "No fingerprint!") - self.assertEqual( - ["The authenticity of host 'fake.example.com (9.8.7.6)' " - "can't be established.\n" - "RSA key fingerprint is " - "57:a1:c2:a1:07:a0:2b:f4:ce:b5:e5:b7:ae:cc:e1:99.\n" - "Are you sure you want to continue connecting (yes/no)? "], - self.fakeFile.outchunks) - return self.assertFailure(d, UserRejectedKey) - - - def test_verifyBadKey(self): - """ - L{default.verifyHostKey} should return a L{Deferred} which fails with - L{HostKeyChanged} if the host key is incorrect. - """ - d = default.verifyHostKey( - self.fakeTransport, "4.3.2.1", otherSampleKey, - "Again, not required.") - return self.assertFailure(d, HostKeyChanged) |