Skip to content

Commit 93a2c31

Browse files
committed
Add support for ECDSA signatures in web editor
1 parent 2b76fa8 commit 93a2c31

3 files changed

Lines changed: 86 additions & 50 deletions

File tree

src/socket/worker.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ const STOP_LISTENING = false;
1212
let globalSocket;
1313

1414
class SocketInterface {
15-
constructor(socket, keys, pluginKey) {
15+
constructor(socket, signVerifyAlgorithm, keys, pluginKey) {
1616
this.socket = socket;
17+
this.signVerifyAlgorithm = signVerifyAlgorithm;
1718
this.keys = keys;
1819
this.pluginKey = pluginKey;
1920

@@ -32,7 +33,7 @@ class SocketInterface {
3233

3334
// sign the message with the editor private key
3435
const signature = await crypto.subtle.sign(
35-
'RSASSA-PKCS1-v1_5',
36+
this.signVerifyAlgorithm,
3637
this.keys.privateKey,
3738
new TextEncoder().encode(encoded),
3839
);
@@ -70,7 +71,7 @@ class SocketInterface {
7071
// verify that the message was sent by the plugin
7172
// (check it was signed with the plugin public key)
7273
const verified = await crypto.subtle.verify(
73-
'RSASSA-PKCS1-v1_5',
74+
this.signVerifyAlgorithm,
7475
this.pluginKey,
7576
decode(signature),
7677
new TextEncoder().encode(encodedMessage),
@@ -95,13 +96,13 @@ class SocketInterface {
9596
}
9697

9798
// eslint-disable-next-line max-len
98-
function socketConnect(channelId, sessionId, keys, pluginKey, userAgent, callbacks) {
99+
function socketConnect(channelId, sessionId, signVerifyAlgorithm, keys, pluginKey, userAgent, callbacks) {
99100
console.log('[WS] Creating socket...');
100101

101102
// create a websocket
102103
// important that no async/await occurs between here and the listener registrations
103104
const socket = new WebSocket(`wss://${config.bytesocks_host}/${channelId}`);
104-
const socketInterface = new SocketInterface(socket, keys, pluginKey);
105+
const socketInterface = new SocketInterface(socket, signVerifyAlgorithm, keys, pluginKey);
105106

106107
socket.onmessage = (event) => {
107108
const frame = JSON.parse(event.data);

src/socket/ws.js

Lines changed: 79 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@ import Worker from 'worker-loader!./worker';
55

66
/* eslint-disable no-use-before-define */
77

8-
export async function socketConnect(channelId, sessionId, pluginPublicKey, callbacks) {
8+
export async function socketConnect(protocolVersion, channelId, sessionId, pluginPublicKey, callbacks) {
9+
const cryptoHelper = new CryptoHelper(protocolVersion);
10+
911
// generate public/private keypair for the editor
10-
const keys = await loadKeys() || await generateKeys();
12+
const keys = await cryptoHelper.loadKeys() || await cryptoHelper.generateKeys();
1113

1214
// decode and import the plugin public key
13-
const pluginKey = await importKey('spki', pluginPublicKey, ['verify']);
15+
const pluginKey = await cryptoHelper.importKey('spki', pluginPublicKey, ['verify']);
1416

1517
const { userAgent } = window.navigator;
1618
const socket = wrap(new Worker());
17-
await socket.socketConnect(channelId, sessionId, keys, pluginKey, userAgent, proxy({
19+
await socket.socketConnect(channelId, sessionId, cryptoHelper.signVerifyAlgorithm, keys, pluginKey, userAgent, proxy({
1820
connect: proxy(() => {
1921
callbacks.connect({ socket });
2022
}),
@@ -34,53 +36,85 @@ export async function socketConnect(channelId, sessionId, pluginPublicKey, callb
3436
return socket;
3537
}
3638

37-
async function loadKeys() {
38-
const encodedPublicKey = localStorage.getItem('editor-public-key');
39-
const encodedPrivateKey = localStorage.getItem('editor-private-key');
39+
class CryptoHelper {
40+
constructor(protocolVersion) {
41+
if (protocolVersion === 1 ) {
42+
this.publicKeyVariable = 'editor-public-key'
43+
this.privateKeyVariable = 'editor-private-key'
44+
this.importAlgorithm = {
45+
name: 'RSASSA-PKCS1-v1_5',
46+
hash: 'SHA-256',
47+
}
48+
this.generateAlgorithm = {
49+
name: 'RSASSA-PKCS1-v1_5',
50+
hash: 'SHA-256',
51+
modulusLength: 4096,
52+
publicExponent: new Uint8Array([1, 0, 1]),
53+
}
54+
this.signVerifyAlgorithm = {
55+
name: 'RSASSA-PKCS1-v1_5',
56+
}
57+
58+
} else if (protocolVersion === 2) {
59+
this.publicKeyVariable = 'editor-public-key-v2'
60+
this.privateKeyVariable = 'editor-private-key-v2'
61+
this.importAlgorithm = {
62+
name: 'ECDSA',
63+
namedCurve: 'P-256',
64+
}
65+
this.generateAlgorithm = {
66+
name: 'ECDSA',
67+
namedCurve: 'P-256',
68+
}
69+
this.signVerifyAlgorithm = {
70+
name: 'ECDSA',
71+
hash: 'SHA-256',
72+
}
73+
} else {
74+
throw new Error(`Unsupported protocol version: ${protocolVersion}`);
75+
}
76+
}
77+
78+
async loadKeys() {
79+
const encodedPublicKey = localStorage.getItem(this.publicKeyVariable);
80+
const encodedPrivateKey = localStorage.getItem(this.privateKeyVariable);
81+
82+
if (encodedPublicKey && encodedPrivateKey) {
83+
const publicKey = await this.importKey('spki', encodedPublicKey, []);
84+
const privateKey = await this.importKey('pkcs8', encodedPrivateKey, ['sign']);
85+
return {
86+
publicKey,
87+
privateKey,
88+
encodedPublicKey,
89+
encodedPrivateKey,
90+
};
91+
}
92+
93+
return null;
94+
}
95+
96+
importKey(format, encoded, keyUsages) {
97+
return crypto.subtle.importKey(format, decode(encoded), this.importAlgorithm, false, keyUsages);
98+
}
99+
100+
async exportKey(format, key, storageKey) {
101+
const exported = await crypto.subtle.exportKey(format, key);
102+
const encoded = encode(exported);
103+
localStorage.setItem(storageKey, encoded);
104+
return encoded;
105+
}
106+
107+
async generateKeys() {
108+
const { publicKey, privateKey } = await crypto.subtle.generateKey(this.generateAlgorithm, true, ['sign']);
109+
110+
const encodedPublicKey = await this.exportKey('spki', publicKey, this.publicKeyVariable);
111+
const encodedPrivateKey = await this.exportKey('pkcs8', privateKey, this.privateKeyVariable);
40112

41-
if (encodedPublicKey && encodedPrivateKey) {
42-
const publicKey = await importKey('spki', encodedPublicKey, []);
43-
const privateKey = await importKey('pkcs8', encodedPrivateKey, ['sign']);
44113
return {
45114
publicKey,
46115
privateKey,
47116
encodedPublicKey,
48117
encodedPrivateKey,
49118
};
50119
}
51-
52-
return null;
53-
}
54-
55-
function importKey(format, encoded, keyUsages) {
56-
return crypto.subtle.importKey(format, decode(encoded), {
57-
name: 'RSASSA-PKCS1-v1_5',
58-
hash: 'SHA-256',
59-
}, false, keyUsages);
60-
}
61-
62-
async function exportKey(format, key, storageKey) {
63-
const exported = await crypto.subtle.exportKey(format, key);
64-
const encoded = encode(exported);
65-
localStorage.setItem(storageKey, encoded);
66-
return encoded;
67-
}
68-
69-
async function generateKeys() {
70-
const { publicKey, privateKey } = await crypto.subtle.generateKey({
71-
name: 'RSASSA-PKCS1-v1_5',
72-
modulusLength: 4096,
73-
publicExponent: new Uint8Array([1, 0, 1]),
74-
hash: 'SHA-256',
75-
}, true, ['sign']);
76-
77-
const encodedPublicKey = await exportKey('spki', publicKey, 'editor-public-key');
78-
const encodedPrivateKey = await exportKey('pkcs8', privateKey, 'editor-private-key');
79-
80-
return {
81-
publicKey,
82-
privateKey,
83-
encodedPublicKey,
84-
encodedPrivateKey,
85-
};
86120
}

src/store/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ export default new Vuex.Store({
577577

578578
if (data.socket?.channelId) {
579579
socketConnect(
580+
data.socket.protocolVersion,
580581
data.socket.channelId,
581582
sessionId,
582583
data.socket.publicKey,

0 commit comments

Comments
 (0)