github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/python/bacalhau_sdk/config.py (about) 1 """Utils for the SDK.""" 2 3 import base64 4 import logging 5 import os 6 import stat 7 from pathlib import Path 8 from typing import Union 9 10 import pem 11 from bacalhau_apiclient import Configuration 12 from Crypto.Hash import SHA256 13 from Crypto.PublicKey import RSA 14 from Crypto.Signature import pkcs1_15 15 16 __client_id = None 17 __user_id_key = None 18 bits_per_key = 2048 19 log = logging.getLogger(__name__) 20 log.setLevel(logging.DEBUG) 21 22 23 def get_client_id() -> Union[str, None]: 24 """Return the client ID.""" 25 global __client_id 26 return __client_id 27 28 29 def set_client_id(client_id: str): 30 """Set the client ID.""" 31 global __client_id 32 __client_id = client_id 33 log.debug("set client_id to %s", __client_id) 34 35 36 def get_user_id_key(): 37 """Return the user ID key.""" 38 global __user_id_key 39 return __user_id_key 40 41 42 def set_user_id_key(user_id_key: RSA.RsaKey): 43 """Set the user ID key.""" 44 global __user_id_key 45 __user_id_key = user_id_key 46 47 48 def init_config(): 49 """Initialize the config.""" 50 config_dir = __ensure_config_dir() 51 log.debug("config_dir: {}".format(config_dir)) 52 __ensure_config_file() 53 key_path = __ensure_user_id_key(config_dir) 54 log.debug("key_path: {}".format(key_path)) 55 set_user_id_key(__load_user_id_key(key_path)) 56 log.debug("user_id_key set") 57 set_client_id(__load_client_id(key_path)) 58 log.debug("client_id: {}".format(get_client_id())) 59 60 conf = Configuration() 61 if os.getenv("BACALHAU_API_HOST") and os.getenv("BACALHAU_API_PORT"): 62 conf.host = "http://{}:{}".format(os.getenv("BACALHAU_API_HOST"), os.getenv("BACALHAU_API_PORT")) 63 log.debug("Using BACALHAU_API_HOST and BACALHAU_API_PORT to set host: %s", conf.host) 64 65 log.debug("init config done") 66 return conf 67 68 69 def __ensure_config_dir() -> Path: 70 """Ensure the config directory exists and return its path.""" 71 config_dir_str = os.getenv("BACALHAU_DIR") 72 if config_dir_str == "" or config_dir_str is None: 73 log.debug("BACALHAU_DIR not set, using default of ~/.bacalhau") 74 home_path = Path.home() 75 config_dir = home_path.joinpath(".bacalhau") 76 config_dir.mkdir(mode=700, parents=True, exist_ok=True) 77 else: 78 os.stat(config_dir_str) 79 config_dir = Path(config_dir_str) 80 log.debug("Using config dir: %s", config_dir.absolute().resolve()) 81 return config_dir.absolute().resolve() 82 83 84 def __ensure_config_file() -> str: 85 """Ensure that BACALHAU_DIR/config.yaml exists.""" 86 # warnings.warn("Not implemented - the BACALHAU_DIR/config.yaml config file is not used yet.") 87 return "" 88 89 90 def __ensure_user_id_key(config_dir: Path) -> Path: 91 """Ensure that a default user ID key exists in the config dir.""" 92 key_file_name = "user_id.pem" 93 user_id_key = config_dir.joinpath(key_file_name) 94 if not os.path.isfile(user_id_key): 95 log.info("User ID key not found at %s, generating one.", user_id_key) 96 key = RSA.generate(bits_per_key) 97 with open(user_id_key, "wb") as f: 98 f.write(key.export_key("PEM", pkcs=1)) 99 os.chmod(user_id_key, stat.S_IRUSR | stat.S_IWUSR) 100 else: 101 log.info("Found user ID key at %s", user_id_key) 102 return user_id_key 103 104 105 def __load_user_id_key(key_file: Path) -> RSA.RsaKey: 106 """Return the private key.""" 107 log.debug("Loading user ID key from %s", key_file) 108 with open(key_file, "rb") as f: 109 certs = pem.parse(f.read()) 110 private_key = RSA.import_key(certs[0].as_bytes()) 111 return private_key 112 113 114 def __convert_to_client_id(key: RSA.RsaKey) -> str: 115 """Return the client ID from a public key. 116 117 e.g. `bae8c1b2adfa04cc647a2457e8c0c605cef8ed11bdea5ac1f19f94219d722dfz`. 118 """ 119 der_key = key.public_key() 120 hash_obj = SHA256.new() 121 hash_obj.update(der_key.n.to_bytes(der_key.size_in_bytes(), byteorder="big")) 122 123 return hash_obj.hexdigest() 124 125 126 def __load_client_id(key_path: Path) -> str: 127 """Return the client ID. 128 129 Should be callable without the need of invoking init_config() first. 130 """ 131 key = __load_user_id_key(key_path) 132 return __convert_to_client_id(key) 133 134 135 def sign_for_client(msg: bytes) -> str: 136 """sign_for_client signs a message with the user's private ID key. 137 138 Must be called after init_config(). 139 """ 140 signer = pkcs1_15.new(get_user_id_key()) 141 hash_obj = SHA256.new() 142 hash_obj.update(msg) 143 144 signed_payload = signer.sign(hash_obj) 145 signature = base64.b64encode(signed_payload).decode() 146 147 return signature 148 149 150 def __clean_pem_pub_key(pem_pub_key: str) -> str: 151 """Prepare a public key in the format expected by the API. 152 153 - remove the header and footer 154 - remove the newlines 155 - remove the first 32 characters i.e. `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A`. \ 156 https://stackoverflow.com/questions/8784905/command-line-tool-to-export-rsa-private-key-to-rsapublickey 157 """ 158 pem_public_key = ( 159 pem_pub_key.replace("-----BEGIN PUBLIC KEY-----", "") 160 .replace("-----END PUBLIC KEY-----", "") 161 .replace("\n", "")[32:] 162 ) 163 return pem_public_key 164 165 166 def get_client_public_key() -> str: 167 """Return the client public key.""" 168 public_key = get_user_id_key().publickey() 169 pem_public_key = public_key.export_key("PEM").decode() 170 return __clean_pem_pub_key(pem_public_key)