sign_json.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2020 The Matrix.org Foundation C.I.C.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import argparse
  17. import json
  18. import sys
  19. from json import JSONDecodeError
  20. import yaml
  21. from signedjson.key import read_signing_keys
  22. from signedjson.sign import sign_json
  23. from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
  24. from synapse.crypto.event_signing import add_hashes_and_signatures
  25. from synapse.util import json_encoder
  26. def main() -> None:
  27. parser = argparse.ArgumentParser(
  28. description="""Adds a signature to a JSON object.
  29. Example usage:
  30. $ scripts-dev/sign_json.py -N test -k localhost.signing.key "{}"
  31. {"signatures":{"test":{"ed25519:a_ZnZh":"LmPnml6iM0iR..."}}}
  32. """,
  33. formatter_class=argparse.RawDescriptionHelpFormatter,
  34. )
  35. parser.add_argument(
  36. "-N",
  37. "--server-name",
  38. help="Name to give as the local homeserver. If unspecified, will be "
  39. "read from the config file.",
  40. )
  41. parser.add_argument(
  42. "-k",
  43. "--signing-key-path",
  44. help="Path to the file containing the private ed25519 key to sign the "
  45. "request with.",
  46. )
  47. parser.add_argument(
  48. "-K",
  49. "--signing-key",
  50. help="The private ed25519 key to sign the request with.",
  51. )
  52. parser.add_argument(
  53. "-c",
  54. "--config",
  55. default="homeserver.yaml",
  56. help=(
  57. "Path to synapse config file, from which the server name and/or signing "
  58. "key path will be read. Ignored if --server-name and --signing-key(-path) "
  59. "are both given."
  60. ),
  61. )
  62. parser.add_argument(
  63. "--sign-event-room-version",
  64. type=str,
  65. help=(
  66. "Sign the JSON as an event for the given room version, rather than raw JSON. "
  67. "This means that we will add a 'hashes' object, and redact the event before "
  68. "signing."
  69. ),
  70. )
  71. input_args = parser.add_mutually_exclusive_group()
  72. input_args.add_argument("input_data", nargs="?", help="Raw JSON to be signed.")
  73. input_args.add_argument(
  74. "-i",
  75. "--input",
  76. type=argparse.FileType("r"),
  77. default=sys.stdin,
  78. help=(
  79. "A file from which to read the JSON to be signed. If neither --input nor "
  80. "input_data are given, JSON will be read from stdin."
  81. ),
  82. )
  83. parser.add_argument(
  84. "-o",
  85. "--output",
  86. type=argparse.FileType("w"),
  87. default=sys.stdout,
  88. help="Where to write the signed JSON. Defaults to stdout.",
  89. )
  90. args = parser.parse_args()
  91. if not args.server_name or not (args.signing_key_path or args.signing_key):
  92. read_args_from_config(args)
  93. if args.signing_key:
  94. keys = read_signing_keys([args.signing_key])
  95. else:
  96. with open(args.signing_key_path) as f:
  97. keys = read_signing_keys(f)
  98. json_to_sign = args.input_data
  99. if json_to_sign is None:
  100. json_to_sign = args.input.read()
  101. try:
  102. obj = json.loads(json_to_sign)
  103. except JSONDecodeError as e:
  104. print("Unable to parse input as JSON: %s" % e, file=sys.stderr)
  105. sys.exit(1)
  106. if not isinstance(obj, dict):
  107. print("Input json was not an object", file=sys.stderr)
  108. sys.exit(1)
  109. if args.sign_event_room_version:
  110. room_version = KNOWN_ROOM_VERSIONS.get(args.sign_event_room_version)
  111. if not room_version:
  112. print(
  113. f"Unknown room version {args.sign_event_room_version}", file=sys.stderr
  114. )
  115. sys.exit(1)
  116. add_hashes_and_signatures(room_version, obj, args.server_name, keys[0])
  117. else:
  118. sign_json(obj, args.server_name, keys[0])
  119. for c in json_encoder.iterencode(obj):
  120. args.output.write(c)
  121. args.output.write("\n")
  122. def read_args_from_config(args: argparse.Namespace) -> None:
  123. with open(args.config, "r") as fh:
  124. config = yaml.safe_load(fh)
  125. if not args.server_name:
  126. args.server_name = config["server_name"]
  127. if not args.signing_key_path and not args.signing_key:
  128. if "signing_key" in config:
  129. args.signing_key = config["signing_key"]
  130. elif "signing_key_path" in config:
  131. args.signing_key_path = config["signing_key_path"]
  132. else:
  133. print(
  134. "A signing key must be given on the commandline or in the config file.",
  135. file=sys.stderr,
  136. )
  137. sys.exit(1)
  138. if __name__ == "__main__":
  139. main()