Compare commits
1 Commits
warningfix
...
seif
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4ea552271 |
50
examples/seif_client.js
Normal file
50
examples/seif_client.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Seif Handshake Client Example
|
||||||
|
// Implements the Seif Protocol handshake client side
|
||||||
|
|
||||||
|
var crypto = use('crypto');
|
||||||
|
var json = use('json');
|
||||||
|
var io = use('io');
|
||||||
|
|
||||||
|
// Alice's key pair
|
||||||
|
var alice_keys = crypto.keypair();
|
||||||
|
console.log("Alice public key:", alice_keys.public);
|
||||||
|
console.log("Alice private key:", alice_keys.private);
|
||||||
|
|
||||||
|
// Bob's public key (in real usage, this would be obtained separately)
|
||||||
|
// For this example, we'll use a hardcoded key or read from file
|
||||||
|
var bob_public_key = null;
|
||||||
|
|
||||||
|
// Try to read Bob's public key from file if it exists
|
||||||
|
if (io.exists('bob_public.key')) {
|
||||||
|
bob_public_key = io.slurp('bob_public.key').trim();
|
||||||
|
console.log("Loaded Bob's public key from file:", bob_public_key);
|
||||||
|
} else {
|
||||||
|
// For testing, use the server's public key printed by seif_server.js
|
||||||
|
console.log("Please create bob_public.key with the server's public key");
|
||||||
|
console.log("Run seif_server.js first to get the public key");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate random handshake key
|
||||||
|
var handshake_key = crypto.keypair().public; // Using public key as random 32-byte value
|
||||||
|
console.log("Generated handshake key:", handshake_key);
|
||||||
|
|
||||||
|
console.log("Sending handshake to server...");
|
||||||
|
|
||||||
|
// Contact the server
|
||||||
|
$_.contact((actor, reason) => {
|
||||||
|
if (!actor) {
|
||||||
|
console.error("Could not establish connection:", reason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
address: "localhost",
|
||||||
|
port: 5678,
|
||||||
|
seif: 1,
|
||||||
|
handshake: crypto.encrypt_pk(bob_public_key, handshake_key),
|
||||||
|
payload: crypto.encrypt(handshake_key, alice_keys.public)
|
||||||
|
});
|
||||||
|
|
||||||
|
$_.receiver(e => {
|
||||||
|
console.log("Received message:", e);
|
||||||
|
});
|
||||||
83
examples/seif_handshake_README.md
Normal file
83
examples/seif_handshake_README.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# Seif Handshake Examples
|
||||||
|
|
||||||
|
This directory contains examples demonstrating the Seif Protocol handshake implementation in Prosperon.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `seif_simple.js` - A standalone demonstration of the Seif handshake cryptographic operations
|
||||||
|
- `seif_server.js` - A server that accepts Seif handshake connections
|
||||||
|
- `seif_client.js` - A client that initiates Seif handshake with a server
|
||||||
|
|
||||||
|
## Running the Examples
|
||||||
|
|
||||||
|
### Simple Demo
|
||||||
|
To see the cryptographic operations in action:
|
||||||
|
```bash
|
||||||
|
./prosperon examples/seif_simple.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client-Server Demo
|
||||||
|
|
||||||
|
1. First, start the server:
|
||||||
|
```bash
|
||||||
|
./prosperon examples/seif_server.js
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Note the server's public key that is printed
|
||||||
|
|
||||||
|
3. Create a file `bob_public.key` with the server's public key:
|
||||||
|
```bash
|
||||||
|
echo "SERVER_PUBLIC_KEY_HERE" > bob_public.key
|
||||||
|
```
|
||||||
|
|
||||||
|
4. In another terminal, run the client:
|
||||||
|
```bash
|
||||||
|
./prosperon examples/seif_client.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## The Seif Protocol
|
||||||
|
|
||||||
|
The Seif handshake establishes a secure session in one round trip:
|
||||||
|
|
||||||
|
1. **Alice's Message**:
|
||||||
|
- Generates random `handshake_key`
|
||||||
|
- Sends: `{seif: 1, handshake: encrypt_pk(bob_public, handshake_key), payload: encrypt(handshake_key, alice_public)}`
|
||||||
|
|
||||||
|
2. **Bob's Response**:
|
||||||
|
- Decrypts `handshake_key` using his private key
|
||||||
|
- Decrypts Alice's public key from payload
|
||||||
|
- Generates `session_key`
|
||||||
|
- Sends: `encrypt(handshake_key, {session: encrypt_pk(alice_public, session_key)})`
|
||||||
|
|
||||||
|
3. **Result**: Both parties share `session_key` for symmetric encryption
|
||||||
|
|
||||||
|
## Actor System Integration
|
||||||
|
|
||||||
|
In Prosperon's actor system:
|
||||||
|
- Actor objects can serve as public key identifiers (they contain unique IDs)
|
||||||
|
- The `$_.portal()` function creates a listening endpoint
|
||||||
|
- The `$_.contact()` function initiates connections
|
||||||
|
- Messages are automatically routed through the actor system
|
||||||
|
|
||||||
|
## Security Properties
|
||||||
|
|
||||||
|
- **Authentication**: Both parties prove possession of their private keys
|
||||||
|
- **Forward Secrecy**: Session keys are ephemeral
|
||||||
|
- **Man-in-the-Middle Protection**: Requires knowledge of both private keys
|
||||||
|
- **One Round Trip**: Efficient session establishment
|
||||||
|
|
||||||
|
## Notes on Implementation
|
||||||
|
|
||||||
|
The current implementation uses the actor object's ID as part of the identity system. In a production system, you might want to:
|
||||||
|
|
||||||
|
1. Store the public key as part of the actor's data
|
||||||
|
2. Use a proper key derivation function for session keys
|
||||||
|
3. Add additional metadata in the handshake (timestamps, nonces, etc.)
|
||||||
|
4. Implement key rotation and session management
|
||||||
|
|
||||||
|
The crypto module provides:
|
||||||
|
- `crypto.keypair()` - Generate X25519 key pairs
|
||||||
|
- `crypto.encrypt_pk(public_key, data)` - Public key encryption
|
||||||
|
- `crypto.decrypt_pk(private_key, data)` - Public key decryption
|
||||||
|
- `crypto.encrypt(key, data)` - Symmetric encryption
|
||||||
|
- `crypto.decrypt(key, data)` - Symmetric decryption
|
||||||
85
examples/seif_server.js
Normal file
85
examples/seif_server.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// Seif Handshake Server Example
|
||||||
|
// Implements the Seif Protocol handshake as described in the documentation
|
||||||
|
|
||||||
|
var crypto = use('crypto');
|
||||||
|
var json = use('json');
|
||||||
|
var io = use('io');
|
||||||
|
|
||||||
|
// Server's key pair
|
||||||
|
var server_keys = crypto.keypair();
|
||||||
|
console.log("Server public key:", server_keys.public);
|
||||||
|
console.log("Server private key:", server_keys.private);
|
||||||
|
|
||||||
|
// Store connected clients
|
||||||
|
var clients = {};
|
||||||
|
|
||||||
|
$_.portal(e => {
|
||||||
|
// Verify the handshake message format
|
||||||
|
if (!e.seif || !e.handshake || !e.payload) {
|
||||||
|
send(e, {error:"Invalid Seif handshake format"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.seif !== 1) {
|
||||||
|
send(e, {error:"Unsupported Seif protocol version:", e.seif});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Decrypt the handshake key with server's private key
|
||||||
|
var handshake_key_encrypted = e.handshake;
|
||||||
|
var handshake_key_hex = crypto.decrypt_pk(server_keys.private, handshake_key_encrypted);
|
||||||
|
|
||||||
|
// Convert ArrayBuffer to hex string
|
||||||
|
var handshake_key_bytes = new Uint8Array(handshake_key_hex);
|
||||||
|
var handshake_key = '';
|
||||||
|
for (var i = 0; i < handshake_key_bytes.length; i++) {
|
||||||
|
var hex = handshake_key_bytes[i].toString(16);
|
||||||
|
handshake_key += (hex.length === 1 ? '0' : '') + hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Decrypted handshake key:", handshake_key);
|
||||||
|
|
||||||
|
// Decrypt the payload (client's public key) with handshake key
|
||||||
|
var client_public_encrypted = e.payload;
|
||||||
|
var client_public_buffer = crypto.decrypt(handshake_key, client_public_encrypted);
|
||||||
|
|
||||||
|
// Convert decrypted buffer to string
|
||||||
|
var client_public_bytes = new Uint8Array(client_public_buffer);
|
||||||
|
var client_public = '';
|
||||||
|
for (var i = 0; i < client_public_bytes.length; i++) {
|
||||||
|
client_public += String.fromCharCode(client_public_bytes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Client's public key:", client_public);
|
||||||
|
|
||||||
|
// Generate session key
|
||||||
|
var session_key = crypto.keypair();
|
||||||
|
console.log("Generated session key:", session_key.public);
|
||||||
|
|
||||||
|
// Create response encrypted with handshake key
|
||||||
|
var response = {
|
||||||
|
session: crypto.encrypt_pk(client_public, session_key.public)
|
||||||
|
};
|
||||||
|
|
||||||
|
var response_encrypted = crypto.encrypt(handshake_key, json.encode(response));
|
||||||
|
|
||||||
|
// Send encrypted response
|
||||||
|
send(e, response_encrypted);
|
||||||
|
|
||||||
|
console.log("Handshake complete with client:", client_public);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
send(e, {error:err})
|
||||||
|
}
|
||||||
|
}, 5678);
|
||||||
|
|
||||||
|
// Handle messages from connected clients
|
||||||
|
$_.receiver(e => {
|
||||||
|
if (e.type === 'encrypted_message') {
|
||||||
|
console.log("Received encrypted message");
|
||||||
|
// In a real implementation, decrypt with session key and process
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Seif server listening on port 5678");
|
||||||
118
examples/seif_simple.js
Normal file
118
examples/seif_simple.js
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
// Simplified Seif Handshake Example
|
||||||
|
// This demonstrates the core cryptographic operations of the Seif handshake
|
||||||
|
|
||||||
|
var crypto = use('crypto');
|
||||||
|
var json = use('json');
|
||||||
|
|
||||||
|
console.log("=== Seif Handshake Demo ===\n");
|
||||||
|
|
||||||
|
// Step 1: Generate key pairs for Alice and Bob
|
||||||
|
console.log("1. Generating key pairs...");
|
||||||
|
var alice_keys = crypto.keypair();
|
||||||
|
var bob_keys = crypto.keypair();
|
||||||
|
|
||||||
|
console.log("Alice's public key:", alice_keys.public);
|
||||||
|
console.log("Bob's public key:", bob_keys.public);
|
||||||
|
|
||||||
|
// Step 2: Alice initiates handshake
|
||||||
|
console.log("\n2. Alice initiates handshake...");
|
||||||
|
|
||||||
|
// Alice generates a random handshake key
|
||||||
|
var handshake_key = crypto.keypair().public; // Using public key generation for random 32 bytes
|
||||||
|
console.log("Handshake key:", handshake_key);
|
||||||
|
|
||||||
|
// Alice creates the handshake message
|
||||||
|
var alice_message = {
|
||||||
|
seif: 1,
|
||||||
|
handshake: crypto.encrypt_pk(bob_keys.public, handshake_key),
|
||||||
|
payload: crypto.encrypt(handshake_key, alice_keys.public)
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Alice's message created (encrypted components)");
|
||||||
|
|
||||||
|
// Step 3: Bob processes the handshake
|
||||||
|
console.log("\n3. Bob processes the handshake...");
|
||||||
|
|
||||||
|
// Bob decrypts the handshake key
|
||||||
|
var decrypted_handshake_key_buffer = crypto.decrypt_pk(bob_keys.private, alice_message.handshake);
|
||||||
|
|
||||||
|
// Convert buffer to hex string
|
||||||
|
var handshake_key_bytes = new Uint8Array(decrypted_handshake_key_buffer);
|
||||||
|
var recovered_handshake_key = '';
|
||||||
|
for (var i = 0; i < handshake_key_bytes.length; i++) {
|
||||||
|
var hex = handshake_key_bytes[i].toString(16);
|
||||||
|
recovered_handshake_key += (hex.length === 1 ? '0' : '') + hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Bob recovered handshake key:", recovered_handshake_key);
|
||||||
|
console.log("Keys match:", recovered_handshake_key === handshake_key);
|
||||||
|
|
||||||
|
// Bob decrypts Alice's public key
|
||||||
|
var alice_public_buffer = crypto.decrypt(recovered_handshake_key, alice_message.payload);
|
||||||
|
var alice_public_bytes = new Uint8Array(alice_public_buffer);
|
||||||
|
var recovered_alice_public = '';
|
||||||
|
for (var i = 0; i < alice_public_bytes.length; i++) {
|
||||||
|
recovered_alice_public += String.fromCharCode(alice_public_bytes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Bob recovered Alice's public key:", recovered_alice_public);
|
||||||
|
console.log("Public keys match:", recovered_alice_public === alice_keys.public);
|
||||||
|
|
||||||
|
// Step 4: Bob generates session key and responds
|
||||||
|
console.log("\n4. Bob generates session key and responds...");
|
||||||
|
|
||||||
|
// Generate a random session key
|
||||||
|
var session_key = crypto.keypair().public;
|
||||||
|
console.log("Session key:", session_key);
|
||||||
|
|
||||||
|
// Bob encrypts the session key with Alice's public key
|
||||||
|
var bob_response = {
|
||||||
|
session: crypto.encrypt_pk(alice_keys.public, session_key)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Encrypt the entire response with the handshake key
|
||||||
|
var encrypted_response = crypto.encrypt(handshake_key, json.encode(bob_response));
|
||||||
|
console.log("Bob's encrypted response created");
|
||||||
|
|
||||||
|
// Step 5: Alice processes Bob's response
|
||||||
|
console.log("\n5. Alice processes Bob's response...");
|
||||||
|
|
||||||
|
// Alice decrypts the response
|
||||||
|
var decrypted_response_buffer = crypto.decrypt(handshake_key, encrypted_response);
|
||||||
|
var response_json = '';
|
||||||
|
var response_bytes = new Uint8Array(decrypted_response_buffer);
|
||||||
|
for (var i = 0; i < response_bytes.length; i++) {
|
||||||
|
response_json += String.fromCharCode(response_bytes[i]);
|
||||||
|
}
|
||||||
|
var response_data = json.decode(response_json);
|
||||||
|
|
||||||
|
// Alice decrypts the session key
|
||||||
|
var session_key_buffer = crypto.decrypt_pk(alice_keys.private, response_data.session);
|
||||||
|
var session_key_bytes = new Uint8Array(session_key_buffer);
|
||||||
|
var recovered_session_key = '';
|
||||||
|
for (var i = 0; i < session_key_bytes.length; i++) {
|
||||||
|
var hex = session_key_bytes[i].toString(16);
|
||||||
|
recovered_session_key += (hex.length === 1 ? '0' : '') + hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Alice recovered session key:", recovered_session_key);
|
||||||
|
console.log("Session keys match:", recovered_session_key === session_key);
|
||||||
|
|
||||||
|
// Step 6: Demonstrate secure communication
|
||||||
|
console.log("\n6. Secure communication established!");
|
||||||
|
console.log("Both parties now share the session key and can communicate securely.");
|
||||||
|
|
||||||
|
// Example encrypted message
|
||||||
|
var message = "Hello, this is a secret message!";
|
||||||
|
var encrypted_msg = crypto.encrypt(session_key, message);
|
||||||
|
console.log("\nAlice encrypts:", message);
|
||||||
|
|
||||||
|
var decrypted_msg_buffer = crypto.decrypt(session_key, encrypted_msg);
|
||||||
|
var decrypted_msg = '';
|
||||||
|
var msg_bytes = new Uint8Array(decrypted_msg_buffer);
|
||||||
|
for (var i = 0; i < msg_bytes.length; i++) {
|
||||||
|
decrypted_msg += String.fromCharCode(msg_bytes[i]);
|
||||||
|
}
|
||||||
|
console.log("Bob decrypts:", decrypted_msg);
|
||||||
|
|
||||||
|
console.log("\n=== Seif Handshake Complete ===");
|
||||||
@@ -199,10 +199,240 @@ JSValue js_crypto_random(JSContext *js, JSValue self, int argc, JSValue *argv)
|
|||||||
return JS_NewFloat64(js, val);
|
return JS_NewFloat64(js, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JSValue js_crypto_encrypt_pk(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
return JS_ThrowTypeError(js, "crypto.encrypt_pk: expected 2 arguments (public_key, plaintext)");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t public_key[32];
|
||||||
|
js2crypto(js, argv[0], public_key);
|
||||||
|
|
||||||
|
size_t plaintext_len;
|
||||||
|
uint8_t *plaintext;
|
||||||
|
|
||||||
|
if (JS_IsString(argv[1])) {
|
||||||
|
const char *str = JS_ToCStringLen(js, &plaintext_len, argv[1]);
|
||||||
|
if (!str) return JS_EXCEPTION;
|
||||||
|
plaintext = (uint8_t *)str;
|
||||||
|
} else {
|
||||||
|
plaintext = JS_GetArrayBuffer(js, &plaintext_len, argv[1]);
|
||||||
|
if (!plaintext) {
|
||||||
|
return JS_ThrowTypeError(js, "crypto.encrypt_pk: plaintext must be string or ArrayBuffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate ephemeral keypair
|
||||||
|
uint8_t ephemeral_secret[32];
|
||||||
|
uint8_t ephemeral_public[32];
|
||||||
|
randombytes(ephemeral_secret, 32);
|
||||||
|
ephemeral_secret[0] &= 248;
|
||||||
|
ephemeral_secret[31] &= 127;
|
||||||
|
ephemeral_secret[31] |= 64;
|
||||||
|
crypto_x25519_public_key(ephemeral_public, ephemeral_secret);
|
||||||
|
|
||||||
|
// Compute shared secret
|
||||||
|
uint8_t shared[32];
|
||||||
|
crypto_x25519(shared, ephemeral_secret, public_key);
|
||||||
|
|
||||||
|
// Derive encryption key using BLAKE2b
|
||||||
|
uint8_t key[32];
|
||||||
|
crypto_blake2b(key, 32, shared, 32);
|
||||||
|
|
||||||
|
// Generate random nonce
|
||||||
|
uint8_t nonce[24];
|
||||||
|
randombytes(nonce, 24);
|
||||||
|
|
||||||
|
// Allocate output buffer: ephemeral_public(32) + nonce(24) + ciphertext + mac(16)
|
||||||
|
size_t output_size = 32 + 24 + plaintext_len + 16;
|
||||||
|
uint8_t *output = js_malloc(js, output_size);
|
||||||
|
if (!output) {
|
||||||
|
if (JS_IsString(argv[1])) JS_FreeCString(js, (const char *)plaintext);
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy ephemeral public key and nonce to output
|
||||||
|
memcpy(output, ephemeral_public, 32);
|
||||||
|
memcpy(output + 32, nonce, 24);
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
crypto_aead_lock(output + 32 + 24, output + 32 + 24 + plaintext_len,
|
||||||
|
key, nonce, NULL, 0, plaintext, plaintext_len);
|
||||||
|
|
||||||
|
if (JS_IsString(argv[1])) JS_FreeCString(js, (const char *)plaintext);
|
||||||
|
|
||||||
|
// Wipe sensitive data
|
||||||
|
crypto_wipe(ephemeral_secret, 32);
|
||||||
|
crypto_wipe(shared, 32);
|
||||||
|
crypto_wipe(key, 32);
|
||||||
|
|
||||||
|
JSValue result = JS_NewArrayBufferCopy(js, output, output_size);
|
||||||
|
js_free(js, output);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue js_crypto_decrypt_pk(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
return JS_ThrowTypeError(js, "crypto.decrypt_pk: expected 2 arguments (private_key, ciphertext)");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t private_key[32];
|
||||||
|
js2crypto(js, argv[0], private_key);
|
||||||
|
|
||||||
|
size_t ciphertext_len;
|
||||||
|
uint8_t *ciphertext = JS_GetArrayBuffer(js, &ciphertext_len, argv[1]);
|
||||||
|
if (!ciphertext) {
|
||||||
|
return JS_ThrowTypeError(js, "crypto.decrypt_pk: ciphertext must be ArrayBuffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ciphertext_len < 32 + 24 + 16) {
|
||||||
|
return JS_ThrowTypeError(js, "crypto.decrypt_pk: ciphertext too short");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract ephemeral public key and nonce
|
||||||
|
uint8_t ephemeral_public[32];
|
||||||
|
uint8_t nonce[24];
|
||||||
|
memcpy(ephemeral_public, ciphertext, 32);
|
||||||
|
memcpy(nonce, ciphertext + 32, 24);
|
||||||
|
|
||||||
|
// Compute shared secret
|
||||||
|
uint8_t shared[32];
|
||||||
|
crypto_x25519(shared, private_key, ephemeral_public);
|
||||||
|
|
||||||
|
// Derive decryption key
|
||||||
|
uint8_t key[32];
|
||||||
|
crypto_blake2b(key, 32, shared, 32);
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
size_t plaintext_len = ciphertext_len - 32 - 24 - 16;
|
||||||
|
uint8_t *plaintext = js_malloc(js, plaintext_len);
|
||||||
|
if (!plaintext) {
|
||||||
|
crypto_wipe(shared, 32);
|
||||||
|
crypto_wipe(key, 32);
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = crypto_aead_unlock(plaintext,
|
||||||
|
ciphertext + ciphertext_len - 16,
|
||||||
|
key, nonce, NULL, 0,
|
||||||
|
ciphertext + 32 + 24, plaintext_len);
|
||||||
|
|
||||||
|
crypto_wipe(shared, 32);
|
||||||
|
crypto_wipe(key, 32);
|
||||||
|
|
||||||
|
if (result != 0) {
|
||||||
|
js_free(js, plaintext);
|
||||||
|
return JS_ThrowTypeError(js, "crypto.decrypt_pk: decryption failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue ret = JS_NewArrayBufferCopy(js, plaintext, plaintext_len);
|
||||||
|
js_free(js, plaintext);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue js_crypto_encrypt(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
return JS_ThrowTypeError(js, "crypto.encrypt: expected 2 arguments (key, plaintext)");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t key[32];
|
||||||
|
js2crypto(js, argv[0], key);
|
||||||
|
|
||||||
|
size_t plaintext_len;
|
||||||
|
uint8_t *plaintext;
|
||||||
|
|
||||||
|
if (JS_IsString(argv[1])) {
|
||||||
|
const char *str = JS_ToCStringLen(js, &plaintext_len, argv[1]);
|
||||||
|
if (!str) return JS_EXCEPTION;
|
||||||
|
plaintext = (uint8_t *)str;
|
||||||
|
} else {
|
||||||
|
plaintext = JS_GetArrayBuffer(js, &plaintext_len, argv[1]);
|
||||||
|
if (!plaintext) {
|
||||||
|
return JS_ThrowTypeError(js, "crypto.encrypt: plaintext must be string or ArrayBuffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate random nonce
|
||||||
|
uint8_t nonce[24];
|
||||||
|
randombytes(nonce, 24);
|
||||||
|
|
||||||
|
// Allocate output buffer: nonce(24) + ciphertext + mac(16)
|
||||||
|
size_t output_size = 24 + plaintext_len + 16;
|
||||||
|
uint8_t *output = js_malloc(js, output_size);
|
||||||
|
if (!output) {
|
||||||
|
if (JS_IsString(argv[1])) JS_FreeCString(js, (const char *)plaintext);
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy nonce to output
|
||||||
|
memcpy(output, nonce, 24);
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
crypto_aead_lock(output + 24, output + 24 + plaintext_len,
|
||||||
|
key, nonce, NULL, 0, plaintext, plaintext_len);
|
||||||
|
|
||||||
|
if (JS_IsString(argv[1])) JS_FreeCString(js, (const char *)plaintext);
|
||||||
|
|
||||||
|
JSValue result = JS_NewArrayBufferCopy(js, output, output_size);
|
||||||
|
js_free(js, output);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue js_crypto_decrypt(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
return JS_ThrowTypeError(js, "crypto.decrypt: expected 2 arguments (key, ciphertext)");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t key[32];
|
||||||
|
js2crypto(js, argv[0], key);
|
||||||
|
|
||||||
|
size_t ciphertext_len;
|
||||||
|
uint8_t *ciphertext = JS_GetArrayBuffer(js, &ciphertext_len, argv[1]);
|
||||||
|
if (!ciphertext) {
|
||||||
|
return JS_ThrowTypeError(js, "crypto.decrypt: ciphertext must be ArrayBuffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ciphertext_len < 24 + 16) {
|
||||||
|
return JS_ThrowTypeError(js, "crypto.decrypt: ciphertext too short");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract nonce
|
||||||
|
uint8_t nonce[24];
|
||||||
|
memcpy(nonce, ciphertext, 24);
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
size_t plaintext_len = ciphertext_len - 24 - 16;
|
||||||
|
uint8_t *plaintext = js_malloc(js, plaintext_len);
|
||||||
|
if (!plaintext) {
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = crypto_aead_unlock(plaintext,
|
||||||
|
ciphertext + ciphertext_len - 16,
|
||||||
|
key, nonce, NULL, 0,
|
||||||
|
ciphertext + 24, plaintext_len);
|
||||||
|
|
||||||
|
if (result != 0) {
|
||||||
|
js_free(js, plaintext);
|
||||||
|
return JS_ThrowTypeError(js, "crypto.decrypt: decryption failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue ret = JS_NewArrayBufferCopy(js, plaintext, plaintext_len);
|
||||||
|
js_free(js, plaintext);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static const JSCFunctionListEntry js_crypto_funcs[] = {
|
static const JSCFunctionListEntry js_crypto_funcs[] = {
|
||||||
JS_CFUNC_DEF("keypair", 0, js_crypto_keypair),
|
JS_CFUNC_DEF("keypair", 0, js_crypto_keypair),
|
||||||
JS_CFUNC_DEF("shared", 1, js_crypto_shared),
|
JS_CFUNC_DEF("shared", 1, js_crypto_shared),
|
||||||
JS_CFUNC_DEF("random", 0, js_crypto_random),
|
JS_CFUNC_DEF("random", 0, js_crypto_random),
|
||||||
|
JS_CFUNC_DEF("encrypt_pk", 2, js_crypto_encrypt_pk),
|
||||||
|
JS_CFUNC_DEF("decrypt_pk", 2, js_crypto_decrypt_pk),
|
||||||
|
JS_CFUNC_DEF("encrypt", 2, js_crypto_encrypt),
|
||||||
|
JS_CFUNC_DEF("decrypt", 2, js_crypto_decrypt),
|
||||||
};
|
};
|
||||||
|
|
||||||
JSValue js_crypto_use(JSContext *js)
|
JSValue js_crypto_use(JSContext *js)
|
||||||
|
|||||||
Reference in New Issue
Block a user