keyclient.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  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. import logging
  16. from canonicaljson import json
  17. from twisted.internet import defer, reactor
  18. from twisted.internet.protocol import Factory
  19. from twisted.web.http import HTTPClient
  20. from synapse.http.endpoint import matrix_federation_endpoint
  21. from synapse.util import logcontext
  22. logger = logging.getLogger(__name__)
  23. KEY_API_V1 = b"/_matrix/key/v1/"
  24. @defer.inlineCallbacks
  25. def fetch_server_key(server_name, tls_client_options_factory, path=KEY_API_V1):
  26. """Fetch the keys for a remote server."""
  27. factory = SynapseKeyClientFactory()
  28. factory.path = path
  29. factory.host = server_name
  30. endpoint = matrix_federation_endpoint(
  31. reactor, server_name, tls_client_options_factory, timeout=30
  32. )
  33. for i in range(5):
  34. try:
  35. with logcontext.PreserveLoggingContext():
  36. protocol = yield endpoint.connect(factory)
  37. server_response, server_certificate = yield protocol.remote_key
  38. defer.returnValue((server_response, server_certificate))
  39. except SynapseKeyClientError as e:
  40. logger.exception("Error getting key for %r" % (server_name,))
  41. if e.status.startswith("4"):
  42. # Don't retry for 4xx responses.
  43. raise IOError("Cannot get key for %r" % server_name)
  44. except Exception as e:
  45. logger.exception(e)
  46. raise IOError("Cannot get key for %r" % server_name)
  47. class SynapseKeyClientError(Exception):
  48. """The key wasn't retrieved from the remote server."""
  49. status = None
  50. pass
  51. class SynapseKeyClientProtocol(HTTPClient):
  52. """Low level HTTPS client which retrieves an application/json response from
  53. the server and extracts the X.509 certificate for the remote peer from the
  54. SSL connection."""
  55. timeout = 30
  56. def __init__(self):
  57. self.remote_key = defer.Deferred()
  58. self.host = None
  59. self._peer = None
  60. def connectionMade(self):
  61. self._peer = self.transport.getPeer()
  62. logger.debug("Connected to %s", self._peer)
  63. self.sendCommand(b"GET", self.path)
  64. if self.host:
  65. self.sendHeader(b"Host", self.host)
  66. self.endHeaders()
  67. self.timer = reactor.callLater(
  68. self.timeout,
  69. self.on_timeout
  70. )
  71. def errback(self, error):
  72. if not self.remote_key.called:
  73. self.remote_key.errback(error)
  74. def callback(self, result):
  75. if not self.remote_key.called:
  76. self.remote_key.callback(result)
  77. def handleStatus(self, version, status, message):
  78. if status != b"200":
  79. # logger.info("Non-200 response from %s: %s %s",
  80. # self.transport.getHost(), status, message)
  81. error = SynapseKeyClientError(
  82. "Non-200 response %r from %r" % (status, self.host)
  83. )
  84. error.status = status
  85. self.errback(error)
  86. self.transport.abortConnection()
  87. def handleResponse(self, response_body_bytes):
  88. try:
  89. json_response = json.loads(response_body_bytes)
  90. except ValueError:
  91. # logger.info("Invalid JSON response from %s",
  92. # self.transport.getHost())
  93. self.transport.abortConnection()
  94. return
  95. certificate = self.transport.getPeerCertificate()
  96. self.callback((json_response, certificate))
  97. self.transport.abortConnection()
  98. self.timer.cancel()
  99. def on_timeout(self):
  100. logger.debug(
  101. "Timeout waiting for response from %s: %s",
  102. self.host, self._peer,
  103. )
  104. self.errback(IOError("Timeout waiting for response"))
  105. self.transport.abortConnection()
  106. class SynapseKeyClientFactory(Factory):
  107. def protocol(self):
  108. protocol = SynapseKeyClientProtocol()
  109. protocol.path = self.path
  110. protocol.host = self.host
  111. return protocol