test_jabbersaslmechanisms.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for L{twisted.words.protocols.jabber.sasl_mechanisms}.
  5. """
  6. from __future__ import absolute_import, division
  7. from twisted.trial import unittest
  8. from twisted.python.compat import networkString
  9. from twisted.words.protocols.jabber import sasl_mechanisms
  10. class PlainTests(unittest.TestCase):
  11. """
  12. Tests for L{twisted.words.protocols.jabber.sasl_mechanisms.Plain}.
  13. """
  14. def test_getInitialResponse(self):
  15. """
  16. Test the initial response.
  17. """
  18. m = sasl_mechanisms.Plain(None, u'test', u'secret')
  19. self.assertEqual(m.getInitialResponse(), b'\x00test\x00secret')
  20. class AnonymousTests(unittest.TestCase):
  21. """
  22. Tests for L{twisted.words.protocols.jabber.sasl_mechanisms.Anonymous}.
  23. """
  24. def test_getInitialResponse(self):
  25. """
  26. Test the initial response to be empty.
  27. """
  28. m = sasl_mechanisms.Anonymous()
  29. self.assertEqual(m.getInitialResponse(), None)
  30. class DigestMD5Tests(unittest.TestCase):
  31. """
  32. Tests for L{twisted.words.protocols.jabber.sasl_mechanisms.DigestMD5}.
  33. """
  34. def setUp(self):
  35. self.mechanism = sasl_mechanisms.DigestMD5(
  36. u'xmpp', u'example.org', None, u'test', u'secret')
  37. def test_getInitialResponse(self):
  38. """
  39. Test that no initial response is generated.
  40. """
  41. self.assertIdentical(self.mechanism.getInitialResponse(), None)
  42. def test_getResponse(self):
  43. """
  44. The response to a Digest-MD5 challenge includes the parameters from the
  45. challenge.
  46. """
  47. challenge = (
  48. b'realm="localhost",nonce="1234",qop="auth",charset=utf-8,'
  49. b'algorithm=md5-sess')
  50. directives = self.mechanism._parse(
  51. self.mechanism.getResponse(challenge))
  52. del directives[b"cnonce"], directives[b"response"]
  53. self.assertEqual({
  54. b'username': b'test', b'nonce': b'1234', b'nc': b'00000001',
  55. b'qop': [b'auth'], b'charset': b'utf-8',
  56. b'realm': b'localhost', b'digest-uri': b'xmpp/example.org'
  57. }, directives)
  58. def test_getResponseNonAsciiRealm(self):
  59. """
  60. Bytes outside the ASCII range in the challenge are nevertheless
  61. included in the response.
  62. """
  63. challenge = (b'realm="\xc3\xa9chec.example.org",nonce="1234",'
  64. b'qop="auth",charset=utf-8,algorithm=md5-sess')
  65. directives = self.mechanism._parse(
  66. self.mechanism.getResponse(challenge))
  67. del directives[b"cnonce"], directives[b"response"]
  68. self.assertEqual({
  69. b'username': b'test', b'nonce': b'1234', b'nc': b'00000001',
  70. b'qop': [b'auth'], b'charset': b'utf-8',
  71. b'realm': b'\xc3\xa9chec.example.org',
  72. b'digest-uri': b'xmpp/example.org'}, directives)
  73. def test_getResponseNoRealm(self):
  74. """
  75. The response to a challenge without a realm uses the host part of the
  76. JID as the realm.
  77. """
  78. challenge = b'nonce="1234",qop="auth",charset=utf-8,algorithm=md5-sess'
  79. directives = self.mechanism._parse(
  80. self.mechanism.getResponse(challenge))
  81. self.assertEqual(directives[b'realm'], b'example.org')
  82. def test_getResponseNoRealmIDN(self):
  83. """
  84. If the challenge does not include a realm and the host part of the JID
  85. includes bytes outside of the ASCII range, the response still includes
  86. the host part of the JID as the realm.
  87. """
  88. self.mechanism = sasl_mechanisms.DigestMD5(
  89. u'xmpp', u'\u00e9chec.example.org', None, u'test', u'secret')
  90. challenge = b'nonce="1234",qop="auth",charset=utf-8,algorithm=md5-sess'
  91. directives = self.mechanism._parse(
  92. self.mechanism.getResponse(challenge))
  93. self.assertEqual(directives[b'realm'], b'\xc3\xa9chec.example.org')
  94. def test_getResponseRspauth(self):
  95. """
  96. If the challenge just has a rspauth directive, the response is empty.
  97. """
  98. challenge = \
  99. b'rspauth=cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA=='
  100. response = self.mechanism.getResponse(challenge)
  101. self.assertEqual(b"", response)
  102. def test_calculateResponse(self):
  103. """
  104. The response to a Digest-MD5 challenge is computed according to RFC
  105. 2831.
  106. """
  107. charset = 'utf-8'
  108. nonce = b'OA6MG9tEQGm2hh'
  109. nc = networkString('%08x' % (1,))
  110. cnonce = b'OA6MHXh6VqTrRk'
  111. username = u'\u0418chris'
  112. password = u'\u0418secret'
  113. host = u'\u0418elwood.innosoft.com'
  114. digestURI = u'imap/\u0418elwood.innosoft.com'.encode(charset)
  115. mechanism = sasl_mechanisms.DigestMD5(
  116. b'imap', host, None, username, password)
  117. response = mechanism._calculateResponse(
  118. cnonce, nc, nonce, username.encode(charset),
  119. password.encode(charset), host.encode(charset), digestURI)
  120. self.assertEqual(response, b'7928f233258be88392424d094453c5e3')
  121. def test_parse(self):
  122. """
  123. A challenge can be parsed into a L{dict} with L{bytes} or L{list}
  124. values.
  125. """
  126. challenge = (
  127. b'nonce="1234",qop="auth,auth-conf",charset=utf-8,'
  128. b'algorithm=md5-sess,cipher="des,3des"')
  129. directives = self.mechanism._parse(challenge)
  130. self.assertEqual({
  131. b"algorithm": b"md5-sess", b"nonce": b"1234",
  132. b"charset": b"utf-8", b"qop": [b'auth', b'auth-conf'],
  133. b"cipher": [b'des', b'3des']
  134. }, directives)