mirror of
https://github.com/valitydev/holmes.git
synced 2024-11-06 09:55:19 +00:00
4cdec0411f
* Switch proto upstreams to valitydev: valitydev/bender-proto@38ce3ff valitydev/binbase-proto@9db92d9 valitydev/cds-proto@ed9f907 valitydev/damsel@d384c12 valitydev/fistful-proto@c45166d valitydev/limiter-proto@8c08550 valitydev/machinegun-proto@af57ba1 valitydev/msgpack-proto@8742c7a * Rewrite Dockerfile * Include protocols as full-fledged git repos * Add GH Actions CI workflow
166 lines
4.9 KiB
Python
Executable File
166 lines
4.9 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import getopt
|
|
import subprocess
|
|
import six
|
|
|
|
|
|
def ensure_binary(s, encoding='utf-8', errors='strict'):
|
|
"""Coerce **s** to six.binary_type.
|
|
For Python 2:
|
|
- `unicode` -> encoded to `str`
|
|
- `str` -> `str`
|
|
For Python 3:
|
|
- `str` -> encoded to `bytes`
|
|
- `bytes` -> `bytes`
|
|
"""
|
|
if isinstance(s, six.text_type):
|
|
return s.encode(encoding, errors)
|
|
elif isinstance(s, six.binary_type):
|
|
return s
|
|
else:
|
|
raise TypeError("not expecting type '%s'" % type(s))
|
|
|
|
|
|
def ensure_str(s, encoding='utf-8', errors='strict'):
|
|
"""Coerce *s* to `str`.
|
|
For Python 2:
|
|
- `unicode` -> encoded to `str`
|
|
- `str` -> `str`
|
|
For Python 3:
|
|
- `str` -> `str`
|
|
- `bytes` -> decoded to `str`
|
|
"""
|
|
if not isinstance(s, (six.text_type, six.binary_type)):
|
|
raise TypeError("not expecting type '%s'" % type(s))
|
|
if six.PY2 and isinstance(s, six.text_type):
|
|
s = s.encode(encoding, errors)
|
|
elif six.PY3 and isinstance(s, six.binary_type):
|
|
s = s.decode(encoding, errors)
|
|
return s
|
|
|
|
|
|
def call(args, raw=False, stdin=""):
|
|
if not raw:
|
|
args = args.split(" ")
|
|
handler = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
|
|
stdin = ensure_binary(stdin)
|
|
out, _err = handler.communicate(input=stdin)
|
|
assert out is not None
|
|
out = ensure_str(out)
|
|
if handler.returncode != 0:
|
|
raise Exception("oops args {} failed with code {} and result {}"
|
|
.format(args, handler.returncode, out))
|
|
return out
|
|
|
|
|
|
def call_keyring(cds_address, func, *args):
|
|
thrift_port = os.environ["THRIFT_PORT"]
|
|
json_args = [json.dumps(arg) for arg in args]
|
|
woorl_args = \
|
|
[
|
|
"woorl", "-s", "cds-proto/proto/keyring.thrift",
|
|
"http://{}:{}/v2/keyring".format(cds_address, thrift_port),
|
|
"KeyringManagement", func
|
|
] + json_args
|
|
return call(woorl_args, raw=True)
|
|
|
|
|
|
def decrypt_and_sign(shareholder_id, encrypted_share):
|
|
decrypted_share = strip_line(
|
|
call("step crypto jwe decrypt --key scripts/cds/{}.enc.json".format(shareholder_id),
|
|
stdin=encrypted_share)
|
|
)
|
|
assert decrypted_share != ""
|
|
return strip_line(
|
|
call("step crypto jws sign --key scripts/cds/{}.sig.json -".format(shareholder_id),
|
|
stdin=decrypted_share))
|
|
|
|
|
|
def strip_line(string):
|
|
result = string.strip("\n")
|
|
assert len(result.split("\n")) == 1
|
|
return result
|
|
|
|
|
|
def init(cds_address):
|
|
encrypted_shares_json = call_keyring(cds_address, "StartInit", 2)
|
|
|
|
encrypted_mk_shares = json.loads(encrypted_shares_json)
|
|
result = None
|
|
|
|
shares = {}
|
|
|
|
for encrypted_mk_share in encrypted_mk_shares:
|
|
shareholder_id = encrypted_mk_share['id']
|
|
encrypted_share = encrypted_mk_share['encrypted_share']
|
|
signed_share = {
|
|
"id": shareholder_id,
|
|
"signed_share": decrypt_and_sign(shareholder_id, encrypted_share)
|
|
}
|
|
result = json.loads(call_keyring(cds_address, "ValidateInit", signed_share))
|
|
if "success" not in result and "more_keys_needed" not in result:
|
|
six.print_("Error! Exception returned: {}".format(result))
|
|
exit(1)
|
|
shares[shareholder_id] = signed_share
|
|
assert "success" in result, "Last ValidateInit return not Success: {}".format(result)
|
|
six.print_(json.dumps(shares))
|
|
|
|
|
|
def unlock(cds_address):
|
|
shares = json.loads(sys.stdin.read())
|
|
|
|
call_keyring(cds_address, "StartUnlock")
|
|
|
|
for shareholder_id in list(shares):
|
|
signed_share = {
|
|
"id": shareholder_id,
|
|
"signed_share": shares[shareholder_id]
|
|
}
|
|
result = json.loads(call_keyring(cds_address, "ConfirmUnlock", signed_share))
|
|
if "success" in result:
|
|
break
|
|
elif "more_keys_needed" not in result:
|
|
six.print_("Error! Exception returned: {}".format(result))
|
|
exit(1)
|
|
else:
|
|
six.print_("Keyring is still locked")
|
|
exit(1)
|
|
|
|
|
|
def get_state(cds_address):
|
|
six.print_(call_keyring(cds_address, "GetState"))
|
|
|
|
|
|
def main(argv):
|
|
address = os.environ["CDS"]
|
|
help_promt = "usage: keyring.py {init | unlock | state}" \
|
|
" [-h | --help | -a <woorl address>| --address <woorl address>]"
|
|
try:
|
|
opts, args = getopt.getopt(argv, "ha:", ["help", "address="])
|
|
except getopt.GetoptError:
|
|
six.print_(help_promt)
|
|
exit(2)
|
|
for opt, arg in opts:
|
|
if opt in ('-h', '--help'):
|
|
six.print_(help_promt)
|
|
exit()
|
|
if opt in ('-a', '--address'):
|
|
address = arg
|
|
if len(args) == 0:
|
|
six.print_(help_promt)
|
|
exit(2)
|
|
elif args[0] == 'init':
|
|
init(address)
|
|
elif args[0] == 'unlock':
|
|
unlock(address)
|
|
elif args[0] == 'state':
|
|
get_state(address)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1:])
|