broadfield-dev commited on
Commit
1770918
Β·
verified Β·
1 Parent(s): e7d2168

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -96
app.py CHANGED
@@ -12,29 +12,22 @@ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
12
  from cryptography.hazmat.primitives import hashes
13
  from cryptography.hazmat.primitives import serialization
14
  from cryptography.hazmat.primitives.asymmetric import rsa, padding
15
- from cryptography.exceptions import InvalidTag
16
 
17
  # --- Configure Logging ---
18
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
19
  logger = logging.getLogger(__name__)
20
 
21
  # ==============================================================================
22
- # CONFIGURATION: URL of the Remote SERVER Service
23
  # ==============================================================================
24
- SERVER_SPACE_ID = "broadfield-dev/KeyLock-Auth-Server"
25
- BASE_HF_URL = "https://huggingface.co/spaces/"
26
- SERVER_URL = f"{BASE_HF_URL}{SERVER_SPACE_ID}"
27
- # The API endpoint is constructed from the server's direct URL, not the hub URL.
28
- SERVER_DIRECT_URL_BASE = f"https://{SERVER_SPACE_ID.replace('/', '-')}.hf.space"
29
- SERVER_API_ENDPOINT = f"{SERVER_DIRECT_URL_BASE}/run/keylock-auth-decoder"
30
 
31
  # ==============================================================================
32
  # LOCAL LOGIC (Key and Image Generation)
33
  # ==============================================================================
34
-
35
  def generate_rsa_keys():
36
  """Generates a new 2048-bit RSA key pair LOCALLY."""
37
- logger.info("Generating new RSA key pair locally.")
38
  private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
39
  private_pem = private_key.private_bytes(
40
  encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8,
@@ -47,7 +40,6 @@ def generate_rsa_keys():
47
 
48
  def create_encrypted_image(secret_data_str: str, public_key_pem: str) -> Image.Image:
49
  """Creates the encrypted image LOCALLY."""
50
- logger.info("Starting local image creation process...")
51
  if not secret_data_str.strip(): raise ValueError("Secret data cannot be empty.")
52
  if not public_key_pem.strip(): raise ValueError("Public Key cannot be empty.")
53
 
@@ -78,19 +70,50 @@ def create_encrypted_image(secret_data_str: str, public_key_pem: str) -> Image.I
78
  binary_payload = ''.join(format(byte, '08b') for byte in struct.pack('>I', len(encrypted_payload)) + encrypted_payload)
79
  if len(binary_payload) > pixel_data.size: raise ValueError("Data too large for image capacity.")
80
  for i in range(len(binary_payload)): pixel_data[i] = (pixel_data[i] & 0xFE) | int(binary_payload[i])
81
-
82
  stego_pixels = pixel_data.reshape((600, 800, 3))
83
  return Image.fromarray(stego_pixels, 'RGB')
84
 
85
  # ==============================================================================
86
- # REMOTE API CALL LOGIC
87
  # ==============================================================================
88
 
89
- def decrypt_image_via_api(image: Image.Image):
90
- """Makes a LIVE API call to the deployed server to decrypt an image."""
91
- if image is None: raise gr.Error("Please provide an image to send.")
92
-
93
- status = f"Connecting to server: {SERVER_SPACE_ID}..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  yield None, status
95
 
96
  try:
@@ -100,31 +123,19 @@ def decrypt_image_via_api(image: Image.Image):
100
 
101
  payload = {"data": [b64_string]}
102
  headers = {"Content-Type": "application/json"}
103
-
104
- status = f"Sending image to API endpoint:\n{SERVER_API_ENDPOINT}"
105
- yield None, status
106
-
107
- response = requests.post(SERVER_API_ENDPOINT, headers=headers, json=payload, timeout=45)
108
  response_json = response.json()
109
 
110
  if response.status_code == 200:
111
  if "data" in response_json:
112
- decrypted_data = response_json["data"][0]
113
- status = "βœ… Success! Data decrypted by the remote server."
114
- return decrypted_data, status
115
- elif "error" in response_json:
116
- raise gr.Error(f"API returned an error: {response_json['error']}")
117
  else:
118
- error_detail = response_json.get("error", "Unknown error.")
119
- raise gr.Error(f"API Error (Status {response.status_code}): {error_detail}")
120
-
121
- except requests.exceptions.RequestException as e:
122
- logger.error(f"Network error calling API: {e}")
123
- raise gr.Error(f"Could not connect to the API. Check the server space is running and the URL is correct. Error: {e}")
124
  except Exception as e:
125
- logger.error(f"An unexpected error occurred: {e}", exc_info=True)
126
- raise gr.Error(f"An unexpected error occurred: {e}")
127
-
128
 
129
  # ==============================================================================
130
  # GRADIO DASHBOARD INTERFACE
@@ -139,83 +150,67 @@ theme = gr.themes.Base(
139
  )
140
 
141
  with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
 
 
142
  gr.Markdown("# πŸ”‘ KeyLock Operations Dashboard")
143
  gr.Markdown("A self-contained dashboard to demonstrate the KeyLock ecosystem. Key/Image creation is performed locally, while decryption is handled by a **live, remote API call** to a secure server.")
144
 
145
  with gr.Tabs() as tabs:
146
- with gr.TabItem("β‘  Generate Keys", id=0):
147
- gr.Markdown("## Step 1: Create a Secure Key Pair (Local)")
148
- gr.Markdown(
149
- """
150
- This tool generates a new RSA key pair within your browser session. In a real-world scenario, the **Private Key** would be immediately stored as a secure secret on a server (like the `KEYLOCK_PRIV_KEY` secret on our demo server), and would never be shown in a UI like this. The **Public Key** would be distributed to clients or other services that need to encrypt data for that server.
151
-
152
- **Action:** Click the button below, then copy both keys for the next steps.
153
- """
154
- )
155
  with gr.Row(variant="panel"):
156
- with gr.Column(scale=1):
157
- gr.Markdown("### Your New Keys")
158
- gen_keys_button = gr.Button("Generate New 2048-bit Key Pair", icon="πŸ”‘", variant="secondary")
159
  with gr.Column(scale=2):
160
- with gr.Row():
161
- output_public_key = gr.Textbox(lines=11, label="Generated Public Key (For Creator)", interactive=False, show_copy_button=True)
162
- output_private_key = gr.Textbox(lines=11, label="Generated Private Key (For Decoder)", interactive=False, show_copy_button=True)
163
-
164
- with gr.TabItem("β‘‘ Create KeyLock", id=1):
165
- gr.Markdown("## Step 2: Create an Encrypted Auth Image (Local)")
166
- gr.Markdown(
167
- """
168
- This tool acts as the **Auth Creator**. It takes your secret data and uses the **Public Key** you generated in Step 1 to encrypt it into a new PNG image. This entire process happens locally in this application. This simulates a user or an automated client preparing credentials to send to the secure server.
169
-
170
- **Action:** Paste the **Public Key** from Step 1, enter some secrets, and click create.
171
- """
172
- )
173
- with gr.Row(variant="panel"):
174
- with gr.Column(scale=1):
175
- gr.Markdown("### Configuration")
176
- creator_pubkey_input = gr.Textbox(lines=8, label="Paste the Public Key Here", placeholder="Copy the public key generated in Step 1...")
177
- creator_secret_input = gr.Textbox(lines=5, label="Secret Data to Encrypt", placeholder="SESSION_ID: abc-123\nUSER: [email protected]")
178
  creator_button = gr.Button("✨ Create Auth Image", variant="primary")
179
  with gr.Column(scale=1):
180
- gr.Markdown("### Output")
181
  creator_status = gr.Textbox(label="Status", interactive=False, lines=2)
182
- creator_image_output = gr.Image(label="Generated Encrypted Image", type="pil", show_download_button=True, format="png", show_share_button=False)
183
-
184
- with gr.TabItem("β‘’ Send KeyLock", id=2):
185
- gr.Markdown("## Step 3: Decrypt via Live API Call")
186
- gr.Markdown(
187
- f"""
188
- This is the core demonstration. This tool acts as a **Client** sending the encrypted image to our live, remote **Server** at [{SERVER_SPACE_ID}]({SERVER_URL}).
189
-
190
- For this demo to work, the **Private Key** you generated in Step 1 must be **the same one** set as the `KEYLOCK_PRIV_KEY` secret in the `{SERVER_SPACE_ID}` Space settings. The client sends the image, and the server uses its own secret key to decrypt it.
191
-
192
- **Action:** Upload the image from Step 2. The dashboard will make a live API call to `{SERVER_API_ENDPOINT}`.
193
- """
194
- )
195
  with gr.Row(variant="panel"):
196
  with gr.Column(scale=1):
197
- gr.Markdown("### Input")
 
 
198
  client_image_input = gr.Image(type="pil", label="Upload or Drag Encrypted Image Here", sources=["upload", "clipboard"])
199
- client_button = gr.Button("πŸ”“ Decrypt Image via Remote Server", variant="primary")
200
  with gr.Column(scale=1):
201
- gr.Markdown("### Decrypted Data")
202
  client_status = gr.Textbox(label="Status", interactive=False, lines=2)
203
- client_json_output = gr.JSON(label="Result from Server")
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
  # --- Wire up the component logic ---
206
  gen_keys_button.click(fn=generate_rsa_keys, inputs=None, outputs=[output_private_key, output_public_key])
207
 
208
- creator_button.click(
209
- fn=create_encrypted_image,
210
- inputs=[creator_pubkey_input, creator_secret_input],
211
- outputs=[creator_image_output, creator_status]
212
- )
213
-
214
- client_button.click(
215
- fn=decrypt_image_via_api,
216
- inputs=[client_image_input],
217
- outputs=[client_json_output, client_status]
218
- )
219
 
220
  if __name__ == "__main__":
221
  demo.launch()
 
12
  from cryptography.hazmat.primitives import hashes
13
  from cryptography.hazmat.primitives import serialization
14
  from cryptography.hazmat.primitives.asymmetric import rsa, padding
 
15
 
16
  # --- Configure Logging ---
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
18
  logger = logging.getLogger(__name__)
19
 
20
  # ==============================================================================
21
+ # CONFIGURATION: URL to the public list of services
22
  # ==============================================================================
23
+ # This can be any publicly accessible raw JSON file.
24
+ ENDPOINTS_JSON_URL = "https://huggingface.co/spaces/broadfield-dev/KeyLock-Auth-Creator/raw/main/endpoints.json"
 
 
 
 
25
 
26
  # ==============================================================================
27
  # LOCAL LOGIC (Key and Image Generation)
28
  # ==============================================================================
 
29
  def generate_rsa_keys():
30
  """Generates a new 2048-bit RSA key pair LOCALLY."""
 
31
  private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
32
  private_pem = private_key.private_bytes(
33
  encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8,
 
40
 
41
  def create_encrypted_image(secret_data_str: str, public_key_pem: str) -> Image.Image:
42
  """Creates the encrypted image LOCALLY."""
 
43
  if not secret_data_str.strip(): raise ValueError("Secret data cannot be empty.")
44
  if not public_key_pem.strip(): raise ValueError("Public Key cannot be empty.")
45
 
 
70
  binary_payload = ''.join(format(byte, '08b') for byte in struct.pack('>I', len(encrypted_payload)) + encrypted_payload)
71
  if len(binary_payload) > pixel_data.size: raise ValueError("Data too large for image capacity.")
72
  for i in range(len(binary_payload)): pixel_data[i] = (pixel_data[i] & 0xFE) | int(binary_payload[i])
 
73
  stego_pixels = pixel_data.reshape((600, 800, 3))
74
  return Image.fromarray(stego_pixels, 'RGB')
75
 
76
  # ==============================================================================
77
+ # UI HELPER & REMOTE API CALL LOGIC
78
  # ==============================================================================
79
 
80
+ def get_server_list():
81
+ """Fetches the list of servers from the public JSON file."""
82
+ status = f"Fetching server list from remote config..."
83
+ yield gr.Dropdown(choices=[], value=None, label="⏳ Fetching..."), status, []
84
+ try:
85
+ response = requests.get(ENDPOINTS_JSON_URL, timeout=10)
86
+ response.raise_for_status()
87
+ endpoints = response.json()
88
+ endpoint_names = [e['name'] for e in endpoints]
89
+ status = f"βœ… Success! Found {len(endpoint_names)} servers."
90
+ yield gr.Dropdown(choices=endpoint_names, value=endpoint_names[0] if endpoint_names else None, label="Target Server"), status, endpoints
91
+ except Exception as e:
92
+ status = f"❌ Error fetching configuration: {e}"
93
+ yield gr.Dropdown(choices=[], value=None, label="Error fetching servers"), status, []
94
+
95
+ def create_keylock_wrapper(service_name: str, secret_data: str, available_endpoints: list):
96
+ """UI wrapper for creating an image for a selected service."""
97
+ if not service_name: raise gr.Error("Please select a target server.")
98
+ public_key = next((e['public_key'] for e in available_endpoints if e['name'] == service_name), None)
99
+ if not public_key: raise gr.Error(f"Could not find public key for '{service_name}'.")
100
+ try:
101
+ created_image = create_encrypted_image(secret_data, public_key)
102
+ return created_image, f"βœ… Success! Image created for '{service_name}'."
103
+ except Exception as e:
104
+ return None, f"❌ Error: {e}"
105
+
106
+ def send_keylock_wrapper(service_name: str, image: Image.Image, available_endpoints: list):
107
+ """UI wrapper for sending an image to a selected server API."""
108
+ if not service_name: raise gr.Error("Please select a target server.")
109
+ if image is None: raise gr.Error("Please upload an image to send.")
110
+
111
+ server = next((e for e in available_endpoints if e['name'] == service_name), None)
112
+ if not server: raise gr.Error(f"Could not find server configuration for '{service_name}'.")
113
+
114
+ server_space_id = server.get('link', '').split('/spaces/')[-1]
115
+ api_endpoint = f"https://{server_space_id.replace('/', '-')}.hf.space/run/keylock-auth-decoder"
116
+ status = f"Connecting to server: {server_space_id}..."
117
  yield None, status
118
 
119
  try:
 
123
 
124
  payload = {"data": [b64_string]}
125
  headers = {"Content-Type": "application/json"}
126
+ response = requests.post(api_endpoint, headers=headers, json=payload, timeout=45)
 
 
 
 
127
  response_json = response.json()
128
 
129
  if response.status_code == 200:
130
  if "data" in response_json:
131
+ return response_json["data"][0], "βœ… Success! Data decrypted by remote server."
132
+ else:
133
+ raise gr.Error(f"API returned an unexpected success format: {response_json}")
 
 
134
  else:
135
+ raise gr.Error(f"API Error (Status {response.status_code}): {response_json.get('error', 'Unknown error')}")
136
+
 
 
 
 
137
  except Exception as e:
138
+ return None, f"❌ Error calling server API: {e}"
 
 
139
 
140
  # ==============================================================================
141
  # GRADIO DASHBOARD INTERFACE
 
150
  )
151
 
152
  with gr.Blocks(theme=theme, title="KeyLock Operations Dashboard") as demo:
153
+ endpoints_state = gr.State([])
154
+
155
  gr.Markdown("# πŸ”‘ KeyLock Operations Dashboard")
156
  gr.Markdown("A self-contained dashboard to demonstrate the KeyLock ecosystem. Key/Image creation is performed locally, while decryption is handled by a **live, remote API call** to a secure server.")
157
 
158
  with gr.Tabs() as tabs:
159
+ with gr.TabItem("β‘  Create KeyLock", id=0):
160
+ gr.Markdown("## Step 1: Create an Encrypted Authentication Image (Local)")
161
+ 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.**")
 
 
 
 
 
 
162
  with gr.Row(variant="panel"):
 
 
 
163
  with gr.Column(scale=2):
164
+ with gr.Row():
165
+ creator_service_dropdown = gr.Dropdown(label="Target Server", interactive=True, info="Select the API server to encrypt data for.")
166
+ refresh_button = gr.Button("πŸ”„", scale=0, size="sm", tooltip="Refresh Server List from Config File")
167
+ creator_secret_input = gr.Textbox(lines=8, label="Secret Data to Encrypt", placeholder="API_KEY: sk-123...\nUSER: demo-user")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  creator_button = gr.Button("✨ Create Auth Image", variant="primary")
169
  with gr.Column(scale=1):
 
170
  creator_status = gr.Textbox(label="Status", interactive=False, lines=2)
171
+ creator_image_output = gr.Image(label="Generated Encrypted Image", type="pil", show_download_button=True, format="png")
172
+
173
+ with gr.TabItem("β‘‘ Send KeyLock", id=1):
174
+ gr.Markdown("## Step 2: Decrypt via Live API Call")
175
+ 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. The server uses its securely stored private key to decrypt the data and sends the result back.")
 
 
 
 
 
 
 
 
176
  with gr.Row(variant="panel"):
177
  with gr.Column(scale=1):
178
+ gr.Markdown("### Select Server to Call")
179
+ send_service_dropdown = gr.Dropdown(label="Target Server", interactive=True, info="Select the API server to send the image to.")
180
+ gr.Markdown("### Upload Image")
181
  client_image_input = gr.Image(type="pil", label="Upload or Drag Encrypted Image Here", sources=["upload", "clipboard"])
182
+ client_button = gr.Button("πŸ”“ Decrypt via Remote Server", variant="primary")
183
  with gr.Column(scale=1):
184
+ gr.Markdown("### Response from Server")
185
  client_status = gr.Textbox(label="Status", interactive=False, lines=2)
186
+ client_json_output = gr.JSON(label="Decrypted Data")
187
+
188
+ with gr.TabItem("ℹ️ Key Generation & Info", id=2):
189
+ gr.Markdown("## Ecosystem Information")
190
+ gr.Markdown("This dashboard coordinates with live Hugging Face Spaces to demonstrate a secure, decoupled workflow.")
191
+ with gr.Accordion("πŸ”‘ RSA Key Pair Generator", open=False):
192
+ gr.Markdown("Create a new public/private key pair. In a real scenario, you would add the **Public Key** to the public `endpoints.json` configuration file, and set the **Private Key** as a secret variable in the corresponding server space.")
193
+ with gr.Row():
194
+ with gr.Column():
195
+ output_public_key = gr.Textbox(lines=10, label="Generated Public Key", interactive=False, show_copy_button=True)
196
+ with gr.Column():
197
+ output_private_key = gr.Textbox(lines=10, label="Generated Private Key", interactive=False, show_copy_button=True)
198
+ gen_keys_button = gr.Button("βš™οΈ Generate New 2048-bit Key Pair", variant="secondary")
199
 
200
  # --- Wire up the component logic ---
201
  gen_keys_button.click(fn=generate_rsa_keys, inputs=None, outputs=[output_private_key, output_public_key])
202
 
203
+ def refresh_and_update_all():
204
+ *_, last_yield = get_server_list()
205
+ dropdown_update, status_update, state_update = last_yield
206
+ # Update both dropdowns simultaneously
207
+ return dropdown_update, dropdown_update, status_update, state_update
208
+
209
+ refresh_button.click(fn=refresh_and_update_all, outputs=[creator_service_dropdown, send_service_dropdown, creator_status, endpoints_state])
210
+ demo.load(fn=refresh_and_update_all, outputs=[creator_service_dropdown, send_service_dropdown, creator_status, endpoints_state])
211
+
212
+ creator_button.click(fn=create_keylock_wrapper, inputs=[creator_service_dropdown, creator_secret_input, endpoints_state], outputs=[creator_image_output, creator_status])
213
+ client_button.click(fn=send_keylock_wrapper, inputs=[send_service_dropdown, client_image_input, endpoints_state], outputs=[client_json_output, client_status])
214
 
215
  if __name__ == "__main__":
216
  demo.launch()