keyclient.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-2016 OpenMarket Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from twisted.web.http import HTTPClient
  16. from twisted.internet.protocol import Factory
  17. from twisted.internet import defer, reactor
  18. from synapse.http.endpoint import matrix_federation_endpoint
  19. from synapse.util.logcontext import (
  20. preserve_context_over_fn, preserve_context_over_deferred
  21. )
  22. import simplejson as json
  23. import logging
  24. logger = logging.getLogger(__name__)
  25. KEY_API_V1 = b"/_matrix/key/v1/"
  26. @defer.inlineCallbacks
  27. def fetch_server_key(server_name, ssl_context_factory, path=KEY_API_V1):
  28. """Fetch the keys for a remote server."""
  29. factory = SynapseKeyClientFactory()
  30. factory.path = path
  31. factory.host = server_name
  32. endpoint = matrix_federation_endpoint(
  33. reactor, server_name, ssl_context_factory, timeout=30
  34. )
  35. for i in range(5):
  36. try:
  37. protocol = yield preserve_context_over_fn(
  38. endpoint.connect, factory
  39. )
  40. server_response, server_certificate = yield preserve_context_over_deferred(
  41. protocol.remote_key
  42. )
  43. defer.returnValue((server_response, server_certificate))
  44. return
  45. except SynapseKeyClientError as e:
  46. logger.exception("Error getting key for %r" % (server_name,))
  47. if e.status.startswith("4"):
  48. # Don't retry for 4xx responses.
  49. raise IOError("Cannot get key for %r" % server_name)
  50. except Exception as e:
  51. logger.exception(e)
  52. raise IOError("Cannot get key for %r" % server_name)
  53. class SynapseKeyClientError(Exception):
  54. """The key wasn't retrieved from the remote server."""
  55. status = None
  56. pass
  57. class SynapseKeyClientProtocol(HTTPClient):
  58. """Low level HTTPS client which retrieves an application/json response from
  59. the server and extracts the X.509 certificate for the remote peer from the
  60. SSL connection."""
  61. timeout = 30
  62. def __init__(self):
  63. self.remote_key = defer.Deferred()
  64. self.host = None
  65. self._peer = None
  66. def connectionMade(self):
  67. self._peer = self.transport.getPeer()
  68. logger.debug("Connected to %s", self._peer)
  69. self.sendCommand(b"GET", self.path)
  70. if self.host:
  71. self.sendHeader(b"Host", self.host)
  72. self.endHeaders()
  73. self.timer = reactor.callLater(
  74. self.timeout,
  75. self.on_timeout
  76. )
  77. def errback(self, error):
  78. if not self.remote_key.called:
  79. self.remote_key.errback(error)
  80. def callback(self, result):
  81. if not self.remote_key.called:
  82. self.remote_key.callback(result)
  83. def handleStatus(self, version, status, message):
  84. if status != b"200":
  85. # logger.info("Non-200 response from %s: %s %s",
  86. # self.transport.getHost(), status, message)
  87. error = SynapseKeyClientError(
  88. "Non-200 response %r from %r" % (status, self.host)
  89. )
  90. error.status = status
  91. self.errback(error)
  92. self.transport.abortConnection()
  93. def handleResponse(self, response_body_bytes):
  94. try:
  95. json_response = json.loads(response_body_bytes)
  96. except ValueError:
  97. # logger.info("Invalid JSON response from %s",
  98. # self.transport.getHost())
  99. self.transport.abortConnection()
  100. return
  101. certificate = self.transport.getPeerCertificate()
  102. self.callback((json_response, certificate))
  103. self.transport.abortConnection()
  104. self.timer.cancel()
  105. def on_timeout(self):
  106. logger.debug(
  107. "Timeout waiting for response from %s: %s",
  108. self.host, self._peer,
  109. )
  110. self.errback(IOError("Timeout waiting for response"))
  111. self.transport.abortConnection()
  112. class SynapseKeyClientFactory(Factory):
  113. def protocol(self):
  114. protocol = SynapseKeyClientProtocol()
  115. protocol.path = self.path
  116. protocol.host = self.host
  117. return protocol