broadfield-dev commited on
Commit
30cc34f
·
verified ·
1 Parent(s): cf47675

Create api/index.js

Browse files
Files changed (1) hide show
  1. api/index.js +82 -0
api/index.js ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // This file is your 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
+ app.use(cors()); // Configure this more strictly for production!
10
+
11
+ // --- Decoder Logic (self-contained in the API file) ---
12
+ function pemToDer(pem) {
13
+ const b64 = pem.replace(/-----BEGIN PRIVATE KEY-----/, '').replace(/-----END PRIVATE KEY-----/, '').replace(/\\n/g, '').replace(/\s/g, '');
14
+ const binaryDer = atob(b64);
15
+ const buffer = new ArrayBuffer(binaryDer.length);
16
+ const bytes = new Uint8Array(buffer);
17
+ for (let i = 0; i < binaryDer.length; i++) { bytes[i] = binaryDer.charCodeAt(i); }
18
+ return buffer;
19
+ }
20
+
21
+ async function decodeFromImageBuffer(imageBuffer, privateKeyPem) {
22
+ // 1. Extract data from image using Sharp
23
+ const { data: pixelData, info } = await sharp(imageBuffer).raw().toBuffer({ resolveWithObject: true });
24
+ const HEADER_BITS = 32;
25
+ let headerBinaryString = '';
26
+ for (let i = 0; headerBinaryString.length < HEADER_BITS; i++) {
27
+ const pixelIndex = i * 4;
28
+ headerBinaryString += pixelData[pixelIndex] & 1;
29
+ if (headerBinaryString.length < HEADER_BITS) headerBinaryString += pixelData[pixelIndex + 1] & 1;
30
+ if (headerBinaryString.length < HEADER_BITS) headerBinaryString += pixelData[pixelIndex + 2] & 1;
31
+ }
32
+ const headerBytes = new Uint8Array(HEADER_BITS / 8);
33
+ for (let i = 0; i < HEADER_BITS / 8; i++) { headerBytes[i] = parseInt(headerBinaryString.substring(i * 8, (i + 1) * 8), 2); }
34
+ const dataLengthInBytes = new DataView(headerBytes.buffer).getUint32(0, false);
35
+ if (dataLengthInBytes === 0) return {};
36
+
37
+ const dataLengthInBits = dataLengthInBytes * 8;
38
+ const bitOffset = HEADER_BITS;
39
+ let dataBinaryString = '';
40
+ for (let i = 0; dataBinaryString.length < dataLengthInBits; i++) {
41
+ const bitPosition = bitOffset + i;
42
+ const pixelIndex = Math.floor(bitPosition / 3) * 4;
43
+ const channelOffset = bitPosition % 3;
44
+ dataBinaryString += pixelData[pixelIndex + channelOffset] & 1;
45
+ }
46
+ const cryptoPayload = new Uint8Array(dataLengthInBytes);
47
+ for (let i = 0; i < dataLengthInBytes; i++) { cryptoPayload[i] = parseInt(dataBinaryString.substring(i * 8, (i + 1) * 8), 2); }
48
+
49
+ // 2. Decrypt payload using Web Crypto API
50
+ const privateKey = await webcrypto.subtle.importKey('pkcs8', pemToDer(privateKeyPem), { name: 'RSA-OAEP', hash: 'SHA-256' }, true, ['decrypt']);
51
+ const encryptedAesKeyLen = new DataView(cryptoPayload.buffer, 0, 4).getUint32(0, false);
52
+ let offset = 4;
53
+ const encryptedAesKey = cryptoPayload.slice(offset, offset + encryptedAesKeyLen);
54
+ offset += encryptedAesKeyLen;
55
+ const nonce = cryptoPayload.slice(offset, offset + 12);
56
+ offset += 12;
57
+ const ciphertextWithTag = cryptoPayload.slice(offset);
58
+ const decryptedAesKeyBytes = await webcrypto.subtle.decrypt({ name: 'RSA-OAEP' }, privateKey, encryptedAesKey);
59
+ const aesKey = await webcrypto.subtle.importKey('raw', decryptedAesKeyBytes, { name: 'AES-GCM', length: 256 }, true, ['decrypt']);
60
+ const decryptedDataBuffer = await webcrypto.subtle.decrypt({ name: 'AES-GCM', iv: nonce }, aesKey, ciphertextWithTag);
61
+ return JSON.parse(new TextDecoder().decode(decryptedDataBuffer));
62
+ }
63
+
64
+ // --- API Endpoint Setup ---
65
+ const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 5 * 1024 * 1024 } });
66
+ app.post('/api/decode', upload.single('authImage'), async (req, res) => {
67
+ const privateKey = process.env.PLUGIN_PRIVATE_KEY;
68
+ if (!privateKey) {
69
+ return res.status(500).json({ error: 'Server is not configured with a private key.' });
70
+ }
71
+ if (!req.file) {
72
+ return res.status(400).json({ error: 'No image file provided.' });
73
+ }
74
+ try {
75
+ const credentials = await decodeFromImageBuffer(req.file.buffer, privateKey);
76
+ res.json({ success: true, data: credentials });
77
+ } catch (error) {
78
+ res.status(400).json({ success: false, error: 'Decryption failed. The image may be corrupt or was not encrypted with the correct public key.' });
79
+ }
80
+ });
81
+
82
+ module.exports = app;