broadfield-dev commited on
Commit
94e6a64
Β·
verified Β·
1 Parent(s): e3a0e49

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +41 -44
app.py CHANGED
@@ -24,11 +24,8 @@ logger = logging.getLogger(__name__)
24
  # ==============================================================================
25
  CREATOR_SPACE_ID = "broadfield-dev/KeyLock-Auth-Creator"
26
  SERVER_SPACE_ID = "broadfield-dev/KeyLock-Auth-Server"
27
-
28
- # URL to the raw JSON file containing the list of public keys and services.
29
  CREATOR_ENDPOINTS_JSON_URL = "https://huggingface.co/spaces/broadfield-dev/KeyLock-Auth-Creator/raw/main/endpoints.json"
30
 
31
- # Construct URLs for linking in documentation
32
  BASE_HF_URL = "https://huggingface.co/spaces/"
33
  CREATOR_URL = f"{BASE_HF_URL}{CREATOR_SPACE_ID}"
34
  SERVER_URL = f"{BASE_HF_URL}{SERVER_SPACE_ID}"
@@ -36,79 +33,70 @@ CREATOR_APP_PY_URL = f"{CREATOR_URL}/blob/main/app.py"
36
  SERVER_APP_PY_URL = f"{SERVER_URL}/blob/main/app.py"
37
 
38
  # ==============================================================================
39
- # LOCAL LOGIC (Key and Image Generation)
40
  # ==============================================================================
41
  def generate_rsa_keys():
42
- """Generates a new 2048-bit RSA key pair LOCALLY."""
43
  private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
44
- private_pem = private_key.private_bytes(
45
- encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8,
46
- encryption_algorithm=serialization.NoEncryption()
47
- ).decode('utf-8')
48
- public_pem = private_key.public_key().public_bytes(
49
- encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo
50
- ).decode('utf-8')
51
  return private_pem, public_pem
52
 
53
  def create_encrypted_image(secret_data_str: str, public_key_pem: str) -> Image.Image:
54
- """Creates the encrypted image LOCALLY."""
55
  if not secret_data_str.strip(): raise ValueError("Secret data cannot be empty.")
56
  if not public_key_pem.strip(): raise ValueError("Public Key cannot be empty.")
57
-
58
- # --- THIS IS THE FIX ---
59
- # Replaced the failing dictionary comprehension with a standard, readable for loop.
60
  data_dict = {}
61
  for line in secret_data_str.splitlines():
62
  line = line.strip()
63
- if not line or line.startswith('#'):
64
- continue
65
-
66
  parts = line.split(':', 1) if ':' in line else line.split('=', 1)
67
- if len(parts) == 2:
68
- key = parts[0].strip()
69
- value = parts[1].strip().strip("'\"")
70
- data_dict[key] = value
71
-
72
  if not data_dict: raise ValueError("No valid key-value pairs found.")
73
-
74
  json_bytes = json.dumps(data_dict).encode('utf-8')
75
  public_key = serialization.load_pem_public_key(public_key_pem.encode('utf-8'))
76
  aes_key, nonce = os.urandom(32), os.urandom(12)
77
  ciphertext = AESGCM(aes_key).encrypt(nonce, json_bytes, None)
78
  rsa_encrypted_key = public_key.encrypt(aes_key, padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None))
79
  encrypted_payload = struct.pack('>I', len(rsa_encrypted_key)) + rsa_encrypted_key + nonce + ciphertext
80
-
81
  img = Image.new('RGB', (800, 600), color=(45, 52, 54))
82
  draw = ImageDraw.Draw(img)
83
  try: font = ImageFont.truetype("DejaVuSans.ttf", 40)
84
  except IOError: font = ImageFont.load_default(size=30)
85
  draw.text((400, 300), "KeyLock Secure Data", fill=(223, 230, 233), font=font, anchor="ms")
86
-
87
  pixel_data = np.array(img.convert("RGB")).ravel()
88
  binary_payload = ''.join(format(b, '08b') for b in struct.pack('>I', len(encrypted_payload)) + encrypted_payload)
89
  if len(binary_payload) > pixel_data.size: raise ValueError("Data too large for image.")
90
  pixel_data[:len(binary_payload)] = (pixel_data[:len(binary_payload)] & 0xFE) | np.array(list(binary_payload), dtype=np.uint8)
91
-
92
  stego_pixels = pixel_data.reshape((600, 800, 3))
93
  return Image.fromarray(stego_pixels, 'RGB')
94
 
95
  # ==============================================================================
96
- # UI HELPER & REMOTE API CALL LOGIC (Your working versions)
97
  # ==============================================================================
98
- def get_creator_endpoints():
99
- """Fetches the list of supported endpoints by making an HTTP request to the Creator's JSON file."""
100
- status = f"Fetching endpoint list from {CREATOR_ENDPOINTS_JSON_URL}..."
101
- yield gr.Dropdown(choices=[], value=None, label="⏳ Fetching..."), status, [] # Initial state
 
 
 
102
  try:
103
  response = requests.get(CREATOR_ENDPOINTS_JSON_URL, timeout=10)
104
  response.raise_for_status()
105
  endpoints = response.json()
 
 
 
 
106
  endpoint_names = [e['name'] for e in endpoints]
107
- status = f"βœ… Success! Found {len(endpoint_names)} endpoints."
108
- yield gr.Dropdown(choices=endpoint_names, value=endpoint_names[0] if endpoint_names else None, label="Target Service"), status, endpoints
 
109
  except Exception as e:
110
- status = f"❌ Error: Could not fetch configuration. Check the URL and if the 'endpoints.json' file is public. Details: {e}"
111
- yield gr.Dropdown(choices=[], value=None, label="Error fetching services"), status, []
 
 
 
112
 
113
  def create_keylock_wrapper(service_name: str, secret_data: str, available_endpoints: list):
114
  """UI wrapper for creating an image for a selected service."""
@@ -117,6 +105,7 @@ def create_keylock_wrapper(service_name: str, secret_data: str, available_endpoi
117
  if not public_key: raise gr.Error(f"Could not find public key for '{service_name}'.")
118
  try:
119
  created_image = create_encrypted_image(secret_data, public_key)
 
120
  return created_image, f"βœ… Success! Image created for '{service_name}'."
121
  except Exception as e:
122
  return None, f"❌ Error: {e}"
@@ -131,6 +120,7 @@ def send_keylock_wrapper(service_name: str, image: Image.Image, available_endpoi
131
  raise gr.Error(f"Configuration Error: Could not find 'api_endpoint' for '{service_name}'.")
132
 
133
  status = f"Connecting to endpoint: {api_endpoint}"
 
134
  yield None, status
135
 
136
  try:
@@ -145,17 +135,17 @@ def send_keylock_wrapper(service_name: str, image: Image.Image, available_endpoi
145
 
146
  if response.status_code == 200:
147
  if "data" in response_json:
148
- return response_json["data"][0], "βœ… Success! Data decrypted by remote server."
149
  else:
150
  raise gr.Error(f"API returned an unexpected success format: {response_json}")
151
  else:
152
  raise gr.Error(f"API Error (Status {response.status_code}): {response_json.get('error', 'Unknown error')}")
153
 
154
  except Exception as e:
155
- return None, f"❌ Error calling server API: {e}"
156
 
157
  # ==============================================================================
158
- # GRADIO DASHBOARD INTERFACE (Your working layout)
159
  # ==============================================================================
160
  theme = gr.themes.Base(
161
  primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.sky, neutral_hue=gr.themes.colors.slate,
@@ -174,13 +164,14 @@ with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
174
 
175
  with gr.Tabs() as tabs:
176
  with gr.TabItem("β‘  Create KeyLock", id=0):
 
177
  gr.Markdown("## Step 1: Create an Encrypted Authentication Image (Local)")
178
- gr.Markdown(f"This tool acts as the **Auth Creator**. It fetches a list of available servers from a public [configuration file]({CREATOR_ENDPOINTS_JSON_URL}), then uses the selected server's public key to encrypt your data into a PNG. **This entire creation process happens locally.**")
179
  with gr.Row(variant="panel"):
180
  with gr.Column(scale=2):
181
  with gr.Row():
182
  creator_service_dropdown = gr.Dropdown(label="Target Server", interactive=True, info="Select the API server to encrypt data for.")
183
- refresh_button = gr.Button("πŸ”„", scale=0, size="sm")
184
  creator_secret_input = gr.Textbox(lines=8, label="Secret Data to Encrypt", placeholder="API_KEY: sk-123...\nUSER: demo-user")
185
  creator_button = gr.Button("✨ Create Auth Image", variant="primary")
186
  with gr.Column(scale=1):
@@ -188,6 +179,7 @@ with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
188
  creator_image_output = gr.Image(label="Generated Encrypted Image", type="pil", show_download_button=True, format="png")
189
 
190
  with gr.TabItem("β‘‘ Send KeyLock", id=1):
 
191
  gr.Markdown("## Step 2: Decrypt via Live API Call")
192
  gr.Markdown("This tool acts as the **Client**. It sends the encrypted image you created in Step 1 to the live, remote **Decoder Server** you select from the same configuration list. The server uses its securely stored private key to decrypt the data and sends the result back.")
193
  with gr.Row(variant="panel"):
@@ -203,8 +195,9 @@ with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
203
  client_json_output = gr.JSON(label="Decrypted Data")
204
 
205
  with gr.TabItem("ℹ️ Info & Key Generation", id=2):
 
206
  gr.Markdown("## Ecosystem Architecture")
207
- gr.Markdown(f"This dashboard uses a public [configuration file]({CREATOR_ENDPOINTS_JSON_URL}) to dynamically discover and interact with live services. It demonstrates a secure, decoupled workflow.")
208
  with gr.Accordion("πŸ”‘ RSA Key Pair Generator", open=False):
209
  gr.Markdown("Create a new key pair. In a real scenario, you would add the **Public Key** and the server's **API Endpoint URL** to the `endpoints.json` configuration file, and set the **Private Key** as a secret variable in the corresponding server space.")
210
  with gr.Row():
@@ -218,7 +211,11 @@ with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
218
  gen_keys_button.click(fn=generate_rsa_keys, inputs=None, outputs=[output_private_key, output_public_key])
219
 
220
  def refresh_and_update_all():
221
- dropdown_update, status_update, state_update = get_server_list()
 
 
 
 
222
  return dropdown_update, dropdown_update, status_update, state_update
223
 
224
  refresh_button.click(fn=refresh_and_update_all, outputs=[creator_service_dropdown, send_service_dropdown, creator_status, endpoints_state])
 
24
  # ==============================================================================
25
  CREATOR_SPACE_ID = "broadfield-dev/KeyLock-Auth-Creator"
26
  SERVER_SPACE_ID = "broadfield-dev/KeyLock-Auth-Server"
 
 
27
  CREATOR_ENDPOINTS_JSON_URL = "https://huggingface.co/spaces/broadfield-dev/KeyLock-Auth-Creator/raw/main/endpoints.json"
28
 
 
29
  BASE_HF_URL = "https://huggingface.co/spaces/"
30
  CREATOR_URL = f"{BASE_HF_URL}{CREATOR_SPACE_ID}"
31
  SERVER_URL = f"{BASE_HF_URL}{SERVER_SPACE_ID}"
 
33
  SERVER_APP_PY_URL = f"{SERVER_URL}/blob/main/app.py"
34
 
35
  # ==============================================================================
36
+ # LOCAL LOGIC (Key and Image Generation) - from your working version
37
  # ==============================================================================
38
  def generate_rsa_keys():
 
39
  private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
40
+ private_pem = private_key.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption()).decode('utf-8')
41
+ public_pem = private_key.public_key().public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo).decode('utf-8')
 
 
 
 
 
42
  return private_pem, public_pem
43
 
44
  def create_encrypted_image(secret_data_str: str, public_key_pem: str) -> Image.Image:
 
45
  if not secret_data_str.strip(): raise ValueError("Secret data cannot be empty.")
46
  if not public_key_pem.strip(): raise ValueError("Public Key cannot be empty.")
 
 
 
47
  data_dict = {}
48
  for line in secret_data_str.splitlines():
49
  line = line.strip()
50
+ if not line or line.startswith('#'): continue
 
 
51
  parts = line.split(':', 1) if ':' in line else line.split('=', 1)
52
+ if len(parts) == 2: data_dict[parts[0].strip()] = parts[1].strip().strip("'\"")
 
 
 
 
53
  if not data_dict: raise ValueError("No valid key-value pairs found.")
 
54
  json_bytes = json.dumps(data_dict).encode('utf-8')
55
  public_key = serialization.load_pem_public_key(public_key_pem.encode('utf-8'))
56
  aes_key, nonce = os.urandom(32), os.urandom(12)
57
  ciphertext = AESGCM(aes_key).encrypt(nonce, json_bytes, None)
58
  rsa_encrypted_key = public_key.encrypt(aes_key, padding.OAEP(mgf=padding.MGF1(hashes.SHA256()), algorithm=hashes.SHA256(), label=None))
59
  encrypted_payload = struct.pack('>I', len(rsa_encrypted_key)) + rsa_encrypted_key + nonce + ciphertext
 
60
  img = Image.new('RGB', (800, 600), color=(45, 52, 54))
61
  draw = ImageDraw.Draw(img)
62
  try: font = ImageFont.truetype("DejaVuSans.ttf", 40)
63
  except IOError: font = ImageFont.load_default(size=30)
64
  draw.text((400, 300), "KeyLock Secure Data", fill=(223, 230, 233), font=font, anchor="ms")
 
65
  pixel_data = np.array(img.convert("RGB")).ravel()
66
  binary_payload = ''.join(format(b, '08b') for b in struct.pack('>I', len(encrypted_payload)) + encrypted_payload)
67
  if len(binary_payload) > pixel_data.size: raise ValueError("Data too large for image.")
68
  pixel_data[:len(binary_payload)] = (pixel_data[:len(binary_payload)] & 0xFE) | np.array(list(binary_payload), dtype=np.uint8)
 
69
  stego_pixels = pixel_data.reshape((600, 800, 3))
70
  return Image.fromarray(stego_pixels, 'RGB')
71
 
72
  # ==============================================================================
73
+ # UI HELPER & REMOTE API CALL LOGIC
74
  # ==============================================================================
75
+
76
+ # --- THIS IS THE MISSING FUNCTION THAT HAS BEEN ADDED BACK ---
77
+ def get_server_list():
78
+ """Fetches the list of servers from the public JSON file."""
79
+ status = f"Fetching server list from remote config..."
80
+ # Yield an initial state to show the UI is working
81
+ yield gr.Dropdown(choices=[], value=None, label="⏳ Fetching..."), status, []
82
  try:
83
  response = requests.get(CREATOR_ENDPOINTS_JSON_URL, timeout=10)
84
  response.raise_for_status()
85
  endpoints = response.json()
86
+ # Validate the structure to prevent downstream errors
87
+ for e in endpoints:
88
+ if not all(k in e for k in ["name", "api_endpoint", "public_key"]):
89
+ raise ValueError(f"Configuration entry is missing required keys: {e}")
90
  endpoint_names = [e['name'] for e in endpoints]
91
+ status = f"βœ… Success! Found {len(endpoint_names)} servers."
92
+ # Yield the final, successful state
93
+ yield gr.Dropdown(choices=endpoint_names, value=endpoint_names[0] if endpoint_names else None, label="Target Server"), status, endpoints
94
  except Exception as e:
95
+ logger.error(f"Failed to get server list: {e}", exc_info=True)
96
+ status = f"❌ Error fetching configuration: {e}"
97
+ # Yield a final error state
98
+ yield gr.Dropdown(choices=[], value=None, label="Error fetching servers"), status, []
99
+
100
 
101
  def create_keylock_wrapper(service_name: str, secret_data: str, available_endpoints: list):
102
  """UI wrapper for creating an image for a selected service."""
 
105
  if not public_key: raise gr.Error(f"Could not find public key for '{service_name}'.")
106
  try:
107
  created_image = create_encrypted_image(secret_data, public_key)
108
+ # Use a single return for robustness
109
  return created_image, f"βœ… Success! Image created for '{service_name}'."
110
  except Exception as e:
111
  return None, f"❌ Error: {e}"
 
120
  raise gr.Error(f"Configuration Error: Could not find 'api_endpoint' for '{service_name}'.")
121
 
122
  status = f"Connecting to endpoint: {api_endpoint}"
123
+ # Use yield to provide an immediate status update
124
  yield None, status
125
 
126
  try:
 
135
 
136
  if response.status_code == 200:
137
  if "data" in response_json:
138
+ yield response_json["data"][0], "βœ… Success! Data decrypted by remote server."
139
  else:
140
  raise gr.Error(f"API returned an unexpected success format: {response_json}")
141
  else:
142
  raise gr.Error(f"API Error (Status {response.status_code}): {response_json.get('error', 'Unknown error')}")
143
 
144
  except Exception as e:
145
+ yield None, f"❌ Error calling server API: {e}"
146
 
147
  # ==============================================================================
148
+ # GRADIO DASHBOARD INTERFACE
149
  # ==============================================================================
150
  theme = gr.themes.Base(
151
  primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.sky, neutral_hue=gr.themes.colors.slate,
 
164
 
165
  with gr.Tabs() as tabs:
166
  with gr.TabItem("β‘  Create KeyLock", id=0):
167
+ # ... UI layout is the same as your working version ...
168
  gr.Markdown("## Step 1: Create an Encrypted Authentication Image (Local)")
169
+ gr.Markdown(f"This tool acts as the **Auth Creator**. It fetches a list of available servers from a public [configuration file]({ENDPOINTS_JSON_URL}), then uses the selected server's public key to encrypt your data into a PNG. **This entire creation process happens locally.**")
170
  with gr.Row(variant="panel"):
171
  with gr.Column(scale=2):
172
  with gr.Row():
173
  creator_service_dropdown = gr.Dropdown(label="Target Server", interactive=True, info="Select the API server to encrypt data for.")
174
+ refresh_button = gr.Button("πŸ”„", scale=0, size="sm", tooltip="Refresh Server List from Config File")
175
  creator_secret_input = gr.Textbox(lines=8, label="Secret Data to Encrypt", placeholder="API_KEY: sk-123...\nUSER: demo-user")
176
  creator_button = gr.Button("✨ Create Auth Image", variant="primary")
177
  with gr.Column(scale=1):
 
179
  creator_image_output = gr.Image(label="Generated Encrypted Image", type="pil", show_download_button=True, format="png")
180
 
181
  with gr.TabItem("β‘‘ Send KeyLock", id=1):
182
+ # ... UI layout is the same as your working version ...
183
  gr.Markdown("## Step 2: Decrypt via Live API Call")
184
  gr.Markdown("This tool acts as the **Client**. It sends the encrypted image you created in Step 1 to the live, remote **Decoder Server** you select from the same configuration list. The server uses its securely stored private key to decrypt the data and sends the result back.")
185
  with gr.Row(variant="panel"):
 
195
  client_json_output = gr.JSON(label="Decrypted Data")
196
 
197
  with gr.TabItem("ℹ️ Info & Key Generation", id=2):
198
+ # ... UI layout is the same as your working version ...
199
  gr.Markdown("## Ecosystem Architecture")
200
+ gr.Markdown(f"This dashboard uses a public [configuration file]({ENDPOINTS_JSON_URL}) to dynamically discover and interact with live services. It demonstrates a secure, decoupled workflow.")
201
  with gr.Accordion("πŸ”‘ RSA Key Pair Generator", open=False):
202
  gr.Markdown("Create a new key pair. In a real scenario, you would add the **Public Key** and the server's **API Endpoint URL** to the `endpoints.json` configuration file, and set the **Private Key** as a secret variable in the corresponding server space.")
203
  with gr.Row():
 
211
  gen_keys_button.click(fn=generate_rsa_keys, inputs=None, outputs=[output_private_key, output_public_key])
212
 
213
  def refresh_and_update_all():
214
+ # The generator must be iterated to get its values
215
+ for dropdown_update, status_update, state_update in get_server_list():
216
+ # This loop will run twice: once for the "loading" state, once for the final state.
217
+ pass # We only care about the final state
218
+ # Update both dropdowns and the state
219
  return dropdown_update, dropdown_update, status_update, state_update
220
 
221
  refresh_button.click(fn=refresh_and_update_all, outputs=[creator_service_dropdown, send_service_dropdown, creator_status, endpoints_state])