broadfield-dev commited on
Commit
8798c7e
·
verified ·
1 Parent(s): ac12f2b

Create index.js

Browse files
Files changed (1) hide show
  1. index.js +91 -0
index.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // The entire server-side API.
2
+ const express = require('express');
3
+ const multer = require('multer');
4
+ const cors = require('cors');
5
+ const sharp = require('sharp');
6
+ const { webcrypto } = require('crypto');
7
+
8
+ const app = express();
9
+ const PORT = process.env.PORT || 7860; // HF Spaces uses port 7860
10
+
11
+ // Allow requests from any origin. For production, you should restrict this
12
+ // to the specific domain of your client application.
13
+ app.use(cors());
14
+
15
+ // --- Full Decoder Logic ---
16
+ function pemToDer(pem) {
17
+ const b64 = pem.replace(/-----BEGIN PRIVATE KEY-----/, '').replace(/-----END PRIVATE KEY-----/, '').replace(/\\n/g, '').replace(/\s/g, '');
18
+ const binaryDer = atob(b64);
19
+ const buffer = new ArrayBuffer(binaryDer.length);
20
+ const bytes = new Uint8Array(buffer);
21
+ for (let i = 0; i < binaryDer.length; i++) { bytes[i] = binaryDer.charCodeAt(i); }
22
+ return buffer;
23
+ }
24
+
25
+ async function decodeFromImageBuffer(imageBuffer, privateKeyPem) {
26
+ // 1. Extract data from image using Sharp
27
+ const { data: pixelData } = await sharp(imageBuffer).raw().toBuffer({ resolveWithObject: true });
28
+ const HEADER_BITS = 32;
29
+ let headerBinaryString = '';
30
+ for (let i = 0; headerBinaryString.length < HEADER_BITS; i++) {
31
+ const pixelIndex = i * 4;
32
+ if(pixelData.length < pixelIndex + 3) throw new Error("Image too small for header.");
33
+ headerBinaryString += pixelData[pixelIndex] & 1;
34
+ if (headerBinaryString.length < HEADER_BITS) headerBinaryString += pixelData[pixelIndex + 1] & 1;
35
+ if (headerBinaryString.length < HEADER_BITS) headerBinaryString += pixelData[pixelIndex + 2] & 1;
36
+ }
37
+ const headerBytes = new Uint8Array(HEADER_BITS / 8);
38
+ for (let i = 0; i < HEADER_BITS / 8; i++) { headerBytes[i] = parseInt(headerBinaryString.substring(i * 8, (i + 1) * 8), 2); }
39
+ const dataLengthInBytes = new DataView(headerBytes.buffer).getUint32(0, false);
40
+ if (dataLengthInBytes === 0) return {};
41
+
42
+ const dataLengthInBits = dataLengthInBytes * 8;
43
+ const bitOffset = HEADER_BITS;
44
+ let dataBinaryString = '';
45
+ for (let i = 0; dataBinaryString.length < dataLengthInBits; i++) {
46
+ const bitPosition = bitOffset + i;
47
+ const pixelIndex = Math.floor(bitPosition / 3) * 4;
48
+ const channelOffset = bitPosition % 3;
49
+ if(pixelData.length < pixelIndex + channelOffset) throw new Error("Image data is corrupt or truncated.");
50
+ dataBinaryString += pixelData[pixelIndex + channelOffset] & 1;
51
+ }
52
+ const cryptoPayload = new Uint8Array(dataLengthInBytes);
53
+ for (let i = 0; i < dataLengthInBytes; i++) { cryptoPayload[i] = parseInt(dataBinaryString.substring(i * 8, (i + 1) * 8), 2); }
54
+
55
+ // 2. Decrypt payload using Web Crypto API
56
+ const privateKey = await webcrypto.subtle.importKey('pkcs8', pemToDer(privateKeyPem), { name: 'RSA-OAEP', hash: 'SHA-256' }, true, ['decrypt']);
57
+ const encryptedAesKeyLen = new DataView(cryptoPayload.buffer, 0, 4).getUint32(0, false);
58
+ let offset = 4;
59
+ const encryptedAesKey = cryptoPayload.slice(offset, offset + encryptedAesKeyLen);
60
+ offset += encryptedAesKeyLen;
61
+ const nonce = cryptoPayload.slice(offset, offset + 12);
62
+ offset += 12;
63
+ const ciphertextWithTag = cryptoPayload.slice(offset);
64
+ const decryptedAesKeyBytes = await webcrypto.subtle.decrypt({ name: 'RSA-OAEP' }, privateKey, encryptedAesKey);
65
+ const aesKey = await webcrypto.subtle.importKey('raw', decryptedAesKeyBytes, { name: 'AES-GCM', length: 256 }, true, ['decrypt']);
66
+ const decryptedDataBuffer = await webcrypto.subtle.decrypt({ name: 'AES-GCM', iv: nonce }, aesKey, ciphertextWithTag);
67
+ return JSON.parse(new TextDecoder().decode(decryptedDataBuffer));
68
+ }
69
+
70
+ // --- API Endpoint ---
71
+ const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 5 * 1024 * 1024 } });
72
+ app.post('/api/decode', upload.single('authImage'), async (req, res) => {
73
+ const privateKey = process.env.PLUGIN_PRIVATE_KEY;
74
+ if (!privateKey) {
75
+ console.error("FATAL: PLUGIN_PRIVATE_KEY secret is not set in the Space settings.");
76
+ return res.status(500).json({ error: 'Server is not configured with a private key.' });
77
+ }
78
+ if (!req.file) {
79
+ return res.status(400).json({ error: 'No image file provided. Please upload a file with the key "authImage".' });
80
+ }
81
+ try {
82
+ const credentials = await decodeFromImageBuffer(req.file.buffer, privateKey);
83
+ res.json({ success: true, data: credentials });
84
+ } catch (error) {
85
+ res.status(400).json({ success: false, error: 'Decryption failed. The image may be corrupt or was not encrypted with the correct public key.' });
86
+ }
87
+ });
88
+
89
+ app.listen(PORT, () => {
90
+ console.log(`Secure decoder API listening on port ${PORT}`);
91
+ });