Changeset fdd7ec6 in trunk


Ignore:
Timestamp:
2021-10-26T13:13:11Z (4 years ago)
Author:
GitHub <noreply@…>
Branches:
master
Children:
f02f14a
Parents:
aa6360f (diff), 5b9997f (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
git-author:
Jean-Paul Calderone <exarkun@…> (2021-10-26 13:13:11)
git-committer:
GitHub <noreply@…> (2021-10-26 13:13:11)
Message:

Merge pull request #35 from tahoe-lafs/LFS-01-020

LFS-01-020: Twisted password check vulnerable to timing attack

Fixes: ticket:3827

Files:
1 added
5 edited

Legend:

Unmodified
Added
Removed
  • TabularUnified docs/frontends/FTP-and-SFTP.rst

    raa6360f rfdd7ec6  
    4848might grant a particular user), and second to decide what directory cap
    4949should be used as the root directory for a log-in by the authenticated user.
    50 A username and password can be used; as of Tahoe-LAFS v1.11, RSA or DSA
    51 public key authentication is also supported.
     50As of Tahoe-LAFS v1.17,
     51RSA/DSA public key authentication is the only supported mechanism.
    5252
    5353Tahoe-LAFS provides two mechanisms to perform this user-to-cap mapping.
     
    6060To use the first form, create a file (for example ``BASEDIR/private/accounts``)
    6161in which each non-comment/non-blank line is a space-separated line of
    62 (USERNAME, PASSWORD, ROOTCAP), like so::
     62(USERNAME, KEY-TYPE, PUBLIC-KEY, ROOTCAP), like so::
    6363
    6464 % cat BASEDIR/private/accounts
    65  # This is a password line: username password cap
    66  alice password URI:DIR2:ioej8xmzrwilg772gzj4fhdg7a:wtiizszzz2rgmczv4wl6bqvbv33ag4kvbr6prz3u6w3geixa6m6a
    67  bob sekrit URI:DIR2:6bdmeitystckbl9yqlw7g56f4e:serp5ioqxnh34mlbmzwvkp3odehsyrr7eytt5f64we3k9hhcrcja
    68 
    6965 # This is a public key line: username keytype pubkey cap
    7066 # (Tahoe-LAFS v1.11 or later)
    7167 carol ssh-rsa AAAA... URI:DIR2:ovjy4yhylqlfoqg2vcze36dhde:4d4f47qko2xm5g7osgo2yyidi5m4muyo2vjjy53q4vjju2u55mfa
    7268
    73 For public key authentication, the keytype may be either "ssh-rsa" or "ssh-dsa".
    74 To avoid ambiguity between passwords and public key types, a password cannot
    75 start with "ssh-".
     69The key type may be either "ssh-rsa" or "ssh-dsa".
    7670
    7771Now add an ``accounts.file`` directive to your ``tahoe.cfg`` file, as described in
  • TabularUnified integration/conftest.py

    raa6360f rfdd7ec6  
    354354    return nodes
    355355
     356@pytest.fixture(scope="session")
     357def alice_sftp_client_key_path(temp_dir):
     358    # The client SSH key path is typically going to be somewhere else (~/.ssh,
     359    # typically), but for convenience sake for testing we'll put it inside node.
     360    return join(temp_dir, "alice", "private", "ssh_client_rsa_key")
    356361
    357362@pytest.fixture(scope='session')
    358363@log_call(action_type=u"integration:alice", include_args=[], include_result=False)
    359 def alice(reactor, temp_dir, introducer_furl, flog_gatherer, storage_nodes, request):
     364def alice(
     365        reactor,
     366        temp_dir,
     367        introducer_furl,
     368        flog_gatherer,
     369        storage_nodes,
     370        alice_sftp_client_key_path,
     371        request,
     372):
    360373    process = pytest_twisted.blockon(
    361374        _create_node(
     
    388401    generate_ssh_key(host_ssh_key_path)
    389402
    390     # 3. Add a SFTP access file with username/password and SSH key auth.
    391 
    392     # The client SSH key path is typically going to be somewhere else (~/.ssh,
    393     # typically), but for convenience sake for testing we'll put it inside node.
    394     client_ssh_key_path = join(process.node_dir, "private", "ssh_client_rsa_key")
    395     generate_ssh_key(client_ssh_key_path)
     403    # 3. Add a SFTP access file with an SSH key for auth.
     404    generate_ssh_key(alice_sftp_client_key_path)
    396405    # Pub key format is "ssh-rsa <thekey> <username>". We want the key.
    397     ssh_public_key = open(client_ssh_key_path + ".pub").read().strip().split()[1]
     406    ssh_public_key = open(alice_sftp_client_key_path + ".pub").read().strip().split()[1]
    398407    with open(accounts_path, "w") as f:
    399408        f.write("""\
    400 alice password {rwcap}
    401 
    402 alice2 ssh-rsa {ssh_public_key} {rwcap}
     409alice-key ssh-rsa {ssh_public_key} {rwcap}
    403410""".format(rwcap=rwcap, ssh_public_key=ssh_public_key))
    404411
  • TabularUnified integration/test_sftp.py

    raa6360f rfdd7ec6  
    2020    from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min  # noqa: F401
    2121
     22import os.path
    2223from posixpath import join
    2324from stat import S_ISDIR
     
    3435
    3536
    36 def connect_sftp(connect_args={"username": "alice", "password": "password"}):
     37def connect_sftp(connect_args):
    3738    """Create an SFTP client."""
    3839    client = SSHClient()
     
    6162def test_bad_account_password_ssh_key(alice, tmpdir):
    6263    """
    63     Can't login with unknown username, wrong password, or wrong SSH pub key.
     64    Can't login with unknown username, any password, or wrong SSH pub key.
    6465    """
    65     # Wrong password, wrong username:
    66     for u, p in [("alice", "wrong"), ("someuser", "password")]:
     66    # Any password, wrong username:
     67    for u, p in [("alice-key", "wrong"), ("someuser", "password")]:
    6768        with pytest.raises(AuthenticationException):
    6869            connect_sftp(connect_args={
     
    7071            })
    7172
    72     another_key = join(str(tmpdir), "ssh_key")
     73    another_key = os.path.join(str(tmpdir), "ssh_key")
    7374    generate_ssh_key(another_key)
    74     good_key = RSAKey(filename=join(alice.node_dir, "private", "ssh_client_rsa_key"))
     75    good_key = RSAKey(filename=os.path.join(alice.node_dir, "private", "ssh_client_rsa_key"))
    7576    bad_key = RSAKey(filename=another_key)
    7677
     
    7879    with pytest.raises(AuthenticationException):
    7980        connect_sftp(connect_args={
    80             "username": "alice2", "pkey": bad_key,
     81            "username": "alice-key", "pkey": bad_key,
    8182        })
    8283
     
    8788        })
    8889
     90def sftp_client_key(node):
     91    return RSAKey(
     92        filename=os.path.join(node.node_dir, "private", "ssh_client_rsa_key"),
     93    )
     94
     95def test_sftp_client_key_exists(alice, alice_sftp_client_key_path):
     96    """
     97    Weakly validate the sftp client key fixture by asserting that *something*
     98    exists at the supposed key path.
     99    """
     100    assert os.path.exists(alice_sftp_client_key_path)
    89101
    90102@run_in_thread
    91103def test_ssh_key_auth(alice):
    92104    """It's possible to login authenticating with SSH public key."""
    93     key = RSAKey(filename=join(alice.node_dir, "private", "ssh_client_rsa_key"))
     105    key = sftp_client_key(alice)
    94106    sftp = connect_sftp(connect_args={
    95         "username": "alice2", "pkey": key
     107        "username": "alice-key", "pkey": key
    96108    })
    97109    assert sftp.listdir() == []
     
    101113def test_read_write_files(alice):
    102114    """It's possible to upload and download files."""
    103     sftp = connect_sftp()
     115    sftp = connect_sftp(connect_args={
     116        "username": "alice-key",
     117        "pkey": sftp_client_key(alice),
     118    })
    104119    with sftp.file("myfile", "wb") as f:
    105120        f.write(b"abc")
     
    118133    them.
    119134    """
    120     sftp = connect_sftp()
     135    sftp = connect_sftp(connect_args={
     136        "username": "alice-key",
     137        "pkey": sftp_client_key(alice),
     138    })
    121139    assert sftp.listdir() == []
    122140
     
    149167def test_rename(alice):
    150168    """Directories and files can be renamed."""
    151     sftp = connect_sftp()
     169    sftp = connect_sftp(connect_args={
     170        "username": "alice-key",
     171        "pkey": sftp_client_key(alice),
     172    })
    152173    sftp.mkdir("dir")
    153174
  • TabularUnified src/allmydata/frontends/auth.py

    raa6360f rfdd7ec6  
    1313from zope.interface import implementer
    1414from twisted.internet import defer
    15 from twisted.cred import error, checkers, credentials
     15from twisted.cred import checkers, credentials
    1616from twisted.conch.ssh import keys
    1717from twisted.conch.checkers import SSHPublicKeyChecker, InMemorySSHKeyDB
     
    3333@implementer(checkers.ICredentialsChecker)
    3434class AccountFileChecker(object):
    35     credentialInterfaces = (credentials.IUsernamePassword,
    36                             credentials.IUsernameHashedPassword,
    37                             credentials.ISSHPrivateKey)
     35    credentialInterfaces = (credentials.ISSHPrivateKey,)
     36
    3837    def __init__(self, client, accountfile):
    3938        self.client = client
    40         self.passwords = BytesKeyDict()
    41         pubkeys = BytesKeyDict()
    42         self.rootcaps = BytesKeyDict()
    43         with open(abspath_expanduser_unicode(accountfile), "rb") as f:
    44             for line in f:
    45                 line = line.strip()
    46                 if line.startswith(b"#") or not line:
    47                     continue
    48                 name, passwd, rest = line.split(None, 2)
    49                 if passwd.startswith(b"ssh-"):
    50                     bits = rest.split()
    51                     keystring = b" ".join([passwd] + bits[:-1])
    52                     key = keys.Key.fromString(keystring)
    53                     rootcap = bits[-1]
    54                     pubkeys[name] = [key]
    55                 else:
    56                     self.passwords[name] = passwd
    57                     rootcap = rest
    58                 self.rootcaps[name] = rootcap
     39        path = abspath_expanduser_unicode(accountfile)
     40        with open_account_file(path) as f:
     41            self.rootcaps, pubkeys = load_account_file(f)
    5942        self._pubkeychecker = SSHPublicKeyChecker(InMemorySSHKeyDB(pubkeys))
    6043
    6144    def _avatarId(self, username):
    6245        return FTPAvatarID(username, self.rootcaps[username])
    63 
    64     def _cbPasswordMatch(self, matched, username):
    65         if matched:
    66             return self._avatarId(username)
    67         raise error.UnauthorizedLogin
    6846
    6947    def requestAvatarId(self, creds):
     
    7250            d.addCallback(self._avatarId)
    7351            return d
    74         elif credentials.IUsernameHashedPassword.providedBy(creds):
    75             return self._checkPassword(creds)
    76         elif credentials.IUsernamePassword.providedBy(creds):
    77             return self._checkPassword(creds)
    78         else:
    79             raise NotImplementedError()
     52        raise NotImplementedError()
    8053
    81     def _checkPassword(self, creds):
    82         """
    83         Determine whether the password in the given credentials matches the
    84         password in the account file.
     54def open_account_file(path):
     55    """
     56    Open and return the accounts file at the given path.
     57    """
     58    return open(path, "rt", encoding="utf-8")
    8559
    86         Returns a Deferred that fires with the username if the password matches
    87         or with an UnauthorizedLogin failure otherwise.
    88         """
    89         try:
    90             correct = self.passwords[creds.username]
    91         except KeyError:
    92             return defer.fail(error.UnauthorizedLogin())
     60def load_account_file(lines):
     61    """
     62    Load credentials from an account file.
    9363
    94         d = defer.maybeDeferred(creds.checkPassword, correct)
    95         d.addCallback(self._cbPasswordMatch, creds.username)
    96         return d
     64    :param lines: An iterable of account lines to load.
     65
     66    :return: See ``create_account_maps``.
     67    """
     68    return create_account_maps(
     69        parse_accounts(
     70            content_lines(
     71                lines,
     72            ),
     73        ),
     74    )
     75
     76def content_lines(lines):
     77    """
     78    Drop empty and commented-out lines (``#``-prefixed) from an iterator of
     79    lines.
     80
     81    :param lines: An iterator of lines to process.
     82
     83    :return: An iterator of lines including only those from ``lines`` that
     84        include content intended to be loaded.
     85    """
     86    for line in lines:
     87        line = line.strip()
     88        if line and not line.startswith("#"):
     89            yield line
     90
     91def parse_accounts(lines):
     92    """
     93    Parse account lines into their components (name, key, rootcap).
     94    """
     95    for line in lines:
     96        name, passwd, rest = line.split(None, 2)
     97        if not passwd.startswith("ssh-"):
     98            raise ValueError(
     99                "Password-based authentication is not supported; "
     100                "configure key-based authentication instead."
     101            )
     102
     103        bits = rest.split()
     104        keystring = " ".join([passwd] + bits[:-1])
     105        key = keys.Key.fromString(keystring)
     106        rootcap = bits[-1]
     107        yield (name, key, rootcap)
     108
     109def create_account_maps(accounts):
     110    """
     111    Build mappings from account names to keys and rootcaps.
     112
     113    :param accounts: An iterator if (name, key, rootcap) tuples.
     114
     115    :return: A tuple of two dicts.  The first maps account names to rootcaps.
     116        The second maps account names to public keys.
     117    """
     118    rootcaps = BytesKeyDict()
     119    pubkeys = BytesKeyDict()
     120    for (name, key, rootcap) in accounts:
     121        name_bytes = name.encode("utf-8")
     122        rootcaps[name_bytes] = rootcap.encode("utf-8")
     123        pubkeys[name_bytes] = [key]
     124    return rootcaps, pubkeys
  • TabularUnified src/allmydata/test/test_auth.py

    raa6360f rfdd7ec6  
    99from future.utils import PY2
    1010if PY2:
    11     from future.builtins import str  # noqa: F401
     11    from future.builtins import str, open  # noqa: F401
     12
     13from hypothesis import (
     14    given,
     15)
     16from hypothesis.strategies import (
     17    text,
     18    characters,
     19    lists,
     20)
    1221
    1322from twisted.trial import unittest
     
    3948""")
    4049
    41 DUMMY_ACCOUNTS = u"""\
    42 alice herpassword URI:DIR2:aaaaaaaaaaaaaaaaaaaaaaaaaa:1111111111111111111111111111111111111111111111111111
    43 bob sekrit URI:DIR2:bbbbbbbbbbbbbbbbbbbbbbbbbb:2222222222222222222222222222222222222222222222222222
    44 
    45 # dennis password URI:DIR2:aaaaaaaaaaaaaaaaaaaaaaaaaa:1111111111111111111111111111111111111111111111111111
     50DUMMY_KEY_DSA = keys.Key.fromString("""\
     51-----BEGIN OPENSSH PRIVATE KEY-----
     52b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdzc2gtZH
     53NzAAAAgQDKMh/ELaiP21LYRBuPbUy7dUhv/XZwV7aS1LzxSP+KaJvtDOei8X76XEAfkqX+
     54aGh9eup+BLkezrV6LlpO9uPzhY8ChlKpkvw5PZKv/2agSrVxZyG7yEzHNtSBQXE6qNMwIk
     55N/ycXLGCqyAhQSzRhLz9ETNaslRDLo7YyVWkiuAQAAABUA5nTatFKux5EqZS4EarMWFRBU
     56i1UAAACAFpkkK+JsPixSTPyn0DNMoGKA0Klqy8h61Ds6pws+4+aJQptUBshpwNw1ypo7MO
     57+goDZy3wwdWtURTPGMgesNdEfxp8L2/kqE4vpMK0myoczCqOiWMeNB/x1AStbSkBI8WmHW
     582htgsC01xbaix/FrA3edK8WEyv+oIxlbV1FkrPkAAACANb0EpCc8uoR4/32rO2JLsbcLBw
     59H5wc2khe7AKkIa9kUknRIRvoCZUtXF5XuXXdRmnpVEm2KcsLdtZjip43asQcqgt0Kz3nuF
     60kAf7bI98G1waFUimcCSPsal4kCmW2HC11sg/BWOt5qczX/0/3xVxpo6juUeBq9ncnFTvPX
     615fOlEAAAHoJkFqHiZBah4AAAAHc3NoLWRzcwAAAIEAyjIfxC2oj9tS2EQbj21Mu3VIb/12
     62cFe2ktS88Uj/imib7QznovF++lxAH5Kl/mhofXrqfgS5Hs61ei5aTvbj84WPAoZSqZL8OT
     632Sr/9moEq1cWchu8hMxzbUgUFxOqjTMCJDf8nFyxgqsgIUEs0YS8/REzWrJUQy6O2MlVpI
     64rgEAAAAVAOZ02rRSrseRKmUuBGqzFhUQVItVAAAAgBaZJCvibD4sUkz8p9AzTKBigNCpas
     65vIetQ7OqcLPuPmiUKbVAbIacDcNcqaOzDvoKA2ct8MHVrVEUzxjIHrDXRH8afC9v5KhOL6
     66TCtJsqHMwqjoljHjQf8dQErW0pASPFph1tobYLAtNcW2osfxawN3nSvFhMr/qCMZW1dRZK
     67z5AAAAgDW9BKQnPLqEeP99qztiS7G3CwcB+cHNpIXuwCpCGvZFJJ0SEb6AmVLVxeV7l13U
     68Zp6VRJtinLC3bWY4qeN2rEHKoLdCs957hZAH+2yPfBtcGhVIpnAkj7GpeJAplthwtdbIPw
     69VjreanM1/9P98VcaaOo7lHgavZ3JxU7z1+XzpRAAAAFQC7360pZLbv7PFt4BPFJ8zAHxAe
     70QwAAAA5leGFya3VuQGJhcnlvbgECAwQ=
     71-----END OPENSSH PRIVATE KEY-----
     72""")
     73
     74ACCOUNTS = u"""\
     75# dennis {key} URI:DIR2:aaaaaaaaaaaaaaaaaaaaaaaaaa:1111111111111111111111111111111111111111111111111111
    4676carol {key} URI:DIR2:cccccccccccccccccccccccccc:3333333333333333333333333333333333333333333333333333
    4777""".format(key=str(DUMMY_KEY.public().toString("openssh"), "ascii")).encode("ascii")
    4878
     79# Python str.splitlines considers NEXT LINE, LINE SEPARATOR, and PARAGRAPH
     80# separator to be line separators, too.  However, file.readlines() does not...
     81LINE_SEPARATORS = (
     82    '\x0a', # line feed
     83    '\x0b', # vertical tab
     84    '\x0c', # form feed
     85    '\x0d', # carriage return
     86)
     87
     88class AccountFileParserTests(unittest.TestCase):
     89    """
     90    Tests for ``load_account_file`` and its helper functions.
     91    """
     92    @given(lists(
     93        text(alphabet=characters(
     94            blacklist_categories=(
     95                # Surrogates are an encoding trick to help out UTF-16.
     96                # They're not necessary to represent any non-surrogate code
     97                # point in unicode.  They're also not legal individually but
     98                # only in pairs.
     99                'Cs',
     100            ),
     101            # Exclude all our line separators too.
     102            blacklist_characters=("\n", "\r"),
     103        )),
     104    ))
     105    def test_ignore_comments(self, lines):
     106        """
     107        ``auth.content_lines`` filters out lines beginning with `#` and empty
     108        lines.
     109        """
     110        expected = set()
     111
     112        # It's not clear that real files and StringIO behave sufficiently
     113        # similarly to use the latter instead of the former here.  In
     114        # particular, they seem to have distinct and incompatible
     115        # line-splitting rules.
     116        bufpath = self.mktemp()
     117        with open(bufpath, "wt", encoding="utf-8") as buf:
     118            for line in lines:
     119                stripped = line.strip()
     120                is_content = stripped and not stripped.startswith("#")
     121                if is_content:
     122                    expected.add(stripped)
     123                buf.write(line + "\n")
     124
     125        with auth.open_account_file(bufpath) as buf:
     126            actual = set(auth.content_lines(buf))
     127
     128        self.assertEqual(expected, actual)
     129
     130    def test_parse_accounts(self):
     131        """
     132        ``auth.parse_accounts`` accepts an iterator of account lines and returns
     133        an iterator of structured account data.
     134        """
     135        alice_key = DUMMY_KEY.public().toString("openssh").decode("utf-8")
     136        alice_cap = "URI:DIR2:aaaa:1111"
     137
     138        bob_key = DUMMY_KEY_DSA.public().toString("openssh").decode("utf-8")
     139        bob_cap = "URI:DIR2:aaaa:2222"
     140        self.assertEqual(
     141            list(auth.parse_accounts([
     142                "alice {} {}".format(alice_key, alice_cap),
     143                "bob {} {}".format(bob_key, bob_cap),
     144            ])),
     145            [
     146                ("alice", DUMMY_KEY.public(), alice_cap),
     147                ("bob", DUMMY_KEY_DSA.public(), bob_cap),
     148            ],
     149        )
     150
     151    def test_parse_accounts_rejects_passwords(self):
     152        """
     153        The iterator returned by ``auth.parse_accounts`` raises ``ValueError``
     154        when processing reaches a line that has what looks like a password
     155        instead of an ssh key.
     156        """
     157        with self.assertRaises(ValueError):
     158            list(auth.parse_accounts(["alice apassword URI:DIR2:aaaa:1111"]))
     159
     160    def test_create_account_maps(self):
     161        """
     162        ``auth.create_account_maps`` accepts an iterator of structured account
     163        data and returns two mappings: one from account name to rootcap, the
     164        other from account name to public keys.
     165        """
     166        alice_cap = "URI:DIR2:aaaa:1111"
     167        alice_key = DUMMY_KEY.public()
     168        bob_cap = "URI:DIR2:aaaa:2222"
     169        bob_key = DUMMY_KEY_DSA.public()
     170        accounts = [
     171            ("alice", alice_key, alice_cap),
     172            ("bob", bob_key, bob_cap),
     173        ]
     174        self.assertEqual(
     175            auth.create_account_maps(accounts),
     176            ({
     177                b"alice": alice_cap.encode("utf-8"),
     178                b"bob": bob_cap.encode("utf-8"),
     179            },
     180             {
     181                 b"alice": [alice_key],
     182                 b"bob": [bob_key],
     183             }),
     184        )
     185
     186    def test_load_account_file(self):
     187        """
     188        ``auth.load_account_file`` accepts an iterator of serialized account lines
     189        and returns two mappings: one from account name to rootcap, the other
     190        from account name to public keys.
     191        """
     192        alice_key = DUMMY_KEY.public().toString("openssh").decode("utf-8")
     193        alice_cap = "URI:DIR2:aaaa:1111"
     194
     195        bob_key = DUMMY_KEY_DSA.public().toString("openssh").decode("utf-8")
     196        bob_cap = "URI:DIR2:aaaa:2222"
     197
     198        accounts = [
     199            "alice {} {}".format(alice_key, alice_cap),
     200            "bob {} {}".format(bob_key, bob_cap),
     201            "# carol {} {}".format(alice_key, alice_cap),
     202        ]
     203
     204        self.assertEqual(
     205            auth.load_account_file(accounts),
     206            ({
     207                b"alice": alice_cap.encode("utf-8"),
     208                b"bob": bob_cap.encode("utf-8"),
     209            },
     210             {
     211                 b"alice": [DUMMY_KEY.public()],
     212                 b"bob": [DUMMY_KEY_DSA.public()],
     213             }),
     214        )
     215
     216
    49217class AccountFileCheckerKeyTests(unittest.TestCase):
    50218    """
     
    53221    def setUp(self):
    54222        self.account_file = filepath.FilePath(self.mktemp())
    55         self.account_file.setContent(DUMMY_ACCOUNTS)
     223        self.account_file.setContent(ACCOUNTS)
    56224        abspath = abspath_expanduser_unicode(str(self.account_file.path))
    57225        self.checker = auth.AccountFileChecker(None, abspath)
    58226
    59     def test_unknown_user_ssh(self):
     227    def test_unknown_user(self):
    60228        """
    61229        AccountFileChecker.requestAvatarId returns a Deferred that fires with
     
    65233        key_credentials = credentials.SSHPrivateKey(
    66234            b"dennis", b"md5", None, None, None)
    67         avatarId = self.checker.requestAvatarId(key_credentials)
    68         return self.assertFailure(avatarId, error.UnauthorizedLogin)
    69 
    70     def test_unknown_user_password(self):
    71         """
    72         AccountFileChecker.requestAvatarId returns a Deferred that fires with
    73         UnauthorizedLogin if called with an SSHPrivateKey object with a
    74         username not present in the account file.
    75 
    76         We use a commented out user, so we're also checking that comments are
    77         skipped.
    78         """
    79         key_credentials = credentials.UsernamePassword(b"dennis", b"password")
    80         d = self.checker.requestAvatarId(key_credentials)
    81         return self.assertFailure(d, error.UnauthorizedLogin)
    82 
    83     def test_password_auth_user_with_ssh_key(self):
    84         """
    85         AccountFileChecker.requestAvatarId returns a Deferred that fires with
    86         UnauthorizedLogin if called with an SSHPrivateKey object for a username
    87         only associated with a password in the account file.
    88         """
    89         key_credentials = credentials.SSHPrivateKey(
    90             b"alice", b"md5", None, None, None)
    91         avatarId = self.checker.requestAvatarId(key_credentials)
    92         return self.assertFailure(avatarId, error.UnauthorizedLogin)
    93 
    94     def test_password_auth_user_with_correct_password(self):
    95         """
    96         AccountFileChecker.requestAvatarId returns a Deferred that fires with
    97         the user if the correct password is given.
    98         """
    99         key_credentials = credentials.UsernamePassword(b"alice", b"herpassword")
    100         d = self.checker.requestAvatarId(key_credentials)
    101         def authenticated(avatarId):
    102             self.assertEqual(
    103                 (b"alice",
    104                  b"URI:DIR2:aaaaaaaaaaaaaaaaaaaaaaaaaa:1111111111111111111111111111111111111111111111111111"),
    105                 (avatarId.username, avatarId.rootcap))
    106         return d
    107 
    108     def test_password_auth_user_with_correct_hashed_password(self):
    109         """
    110         AccountFileChecker.requestAvatarId returns a Deferred that fires with
    111         the user if the correct password is given in hashed form.
    112         """
    113         key_credentials = credentials.UsernameHashedPassword(b"alice", b"herpassword")
    114         d = self.checker.requestAvatarId(key_credentials)
    115         def authenticated(avatarId):
    116             self.assertEqual(
    117                 (b"alice",
    118                  b"URI:DIR2:aaaaaaaaaaaaaaaaaaaaaaaaaa:1111111111111111111111111111111111111111111111111111"),
    119                 (avatarId.username, avatarId.rootcap))
    120         return d
    121 
    122     def test_password_auth_user_with_wrong_password(self):
    123         """
    124         AccountFileChecker.requestAvatarId returns a Deferred that fires with
    125         UnauthorizedLogin if the wrong password is given.
    126         """
    127         key_credentials = credentials.UsernamePassword(b"alice", b"WRONG")
    128235        avatarId = self.checker.requestAvatarId(key_credentials)
    129236        return self.assertFailure(avatarId, error.UnauthorizedLogin)
Note: See TracChangeset for help on using the changeset viewer.