Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dotnet-crypto-worker.js « workers « runtime « wasm « mono « src - github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: f6d66c82da6d52f53627018c13f32e5ab41ed488 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
// @ts-check
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

var ChannelWorker = {
    _impl: class {
        // LOCK states
        get LOCK_UNLOCKED() { return 0; } // 0 means the lock is unlocked
        get LOCK_OWNED() { return 2; } // 2 means the ChannelWorker owns the lock

        // BEGIN ChannelOwner contract - shared constants.
        get STATE_IDX() { return 0; }
        get MSG_SIZE_IDX() { return 1; }
        get LOCK_IDX() { return 2; }

        // Communication states.
        get STATE_SHUTDOWN() { return -1; } // Shutdown
        get STATE_IDLE() { return 0; }
        get STATE_REQ() { return 1; }
        get STATE_RESP() { return 2; }
        get STATE_REQ_P() { return 3; } // Request has multiple parts
        get STATE_RESP_P() { return 4; } // Response has multiple parts
        get STATE_AWAIT() { return 5; } // Awaiting the next part
        // END ChannelOwner contract - shared constants.

        constructor(comm_buf, msg_buf, msg_char_len) {
            this.comm = new Int32Array(comm_buf);
            this.msg = new Uint16Array(msg_buf);
            this.msg_char_len = msg_char_len;
        }

        async await_request(async_call) {
            for (;;) {
                // Wait for signal to perform operation
                Atomics.wait(this.comm, this.STATE_IDX, this.STATE_IDLE);

                // Read in request
                var req = this._read_request();
                if (req === this.STATE_SHUTDOWN)
                    break;

                var resp = null;
                try {
                    // Perform async action based on request
                    resp = await async_call(req);
                }
                catch (err) {
                    console.log("Request error: " + err);
                    resp = JSON.stringify(err);
                }

                // Send response
                this._send_response(resp);
            }
        }

        _read_request() {
            var request = "";
            for (;;) {
                this._acquire_lock();

                // Get the current state and message size
                var state = Atomics.load(this.comm, this.STATE_IDX);
                var size_to_read = Atomics.load(this.comm, this.MSG_SIZE_IDX);

                // Append the latest part of the message.
                request += this._read_from_msg(0, size_to_read);

                // The request is complete.
                if (state === this.STATE_REQ) {
                    this._release_lock();
                    break;
                }

                // Shutdown the worker.
                if (state === this.STATE_SHUTDOWN) {
                    this._release_lock();
                    return this.STATE_SHUTDOWN;
                }

                // Reset the size and transition to await state.
                Atomics.store(this.comm, this.MSG_SIZE_IDX, 0);
                Atomics.store(this.comm, this.STATE_IDX, this.STATE_AWAIT);
                this._release_lock();

                Atomics.wait(this.comm, this.STATE_IDX, this.STATE_AWAIT);
            }

            return request;
        }

        _read_from_msg(begin, end) {
            return String.fromCharCode.apply(null, this.msg.slice(begin, end));
        }

        _send_response(msg) {
            if (Atomics.load(this.comm, this.STATE_IDX) !== this.STATE_REQ)
                throw "WORKER: Invalid sync communication channel state.";

            var state; // State machine variable
            const msg_len = msg.length;
            var msg_written = 0;

            for (;;) {
                this._acquire_lock();

                // Write the message and return how much was written.
                var wrote = this._write_to_msg(msg, msg_written, msg_len);
                msg_written += wrote;

                // Indicate how much was written to the this.msg buffer.
                Atomics.store(this.comm, this.MSG_SIZE_IDX, wrote);

                // Indicate if this was the whole message or part of it.
                state = msg_written === msg_len ? this.STATE_RESP : this.STATE_RESP_P;

                // Update the state
                Atomics.store(this.comm, this.STATE_IDX, state);

                this._release_lock();

                // Wait for the transition to know the main thread has
                // received the response by moving onto a new state.
                Atomics.wait(this.comm, this.STATE_IDX, state);

                // Done sending response.
                if (state === this.STATE_RESP)
                    break;
            }
        }

        _write_to_msg(input, start, input_len) {
            var mi = 0;
            var ii = start;
            while (mi < this.msg_char_len && ii < input_len) {
                this.msg[mi] = input.charCodeAt(ii);
                ii++; // Next character
                mi++; // Next buffer index
            }
            return ii - start;
        }

        _acquire_lock() {
            while (Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_UNLOCKED, this.LOCK_OWNED) !== this.LOCK_UNLOCKED) {
                // empty
            }
        }

        _release_lock() {
            const result = Atomics.compareExchange(this.comm, this.LOCK_IDX, this.LOCK_OWNED, this.LOCK_UNLOCKED);
            if (result !== this.LOCK_OWNED) {
                throw "CRYPTO: ChannelWorker tried to release a lock that wasn't acquired: " + result;
            }
        }
    },

    create: function (comm_buf, msg_buf, msg_char_len) {
        return new this._impl(comm_buf, msg_buf, msg_char_len);
    }
};

async function call_digest(type, data) {
    const digest_type = get_hash_name(type);

    // The 'crypto' API is not available in non-browser
    // environments (for example, v8 server).
    const digest = await crypto.subtle.digest(digest_type, data);
    return Array.from(new Uint8Array(digest));
}

async function sign(type, key, data) {
    const hash_name = get_hash_name(type);

    if (key.length === 0) {
        // crypto.subtle.importKey will raise an error for an empty key.
        // To prevent an error, reset it to a key with just a `0x00` byte. This is equivalent
        // since HMAC keys get zero-extended up to the block size of the algorithm.
        key = new Uint8Array([0]);
    }

    const cryptoKey = await crypto.subtle.importKey("raw", key, {name: "HMAC", hash: hash_name}, false /* extractable */, ["sign"]);
    const signResult = await crypto.subtle.sign("HMAC", cryptoKey, data);
    return Array.from(new Uint8Array(signResult));
}

function get_hash_name(type) {
    switch (type) {
        case 0: return "SHA-1";
        case 1: return "SHA-256";
        case 2: return "SHA-384";
        case 3: return "SHA-512";
        default:
            throw "CRYPTO: Unknown digest: " + type;
    }
}

const AesBlockSizeBytes = 16; // 128 bits

async function encrypt_decrypt(isEncrypting, key, iv, data) {
    const algorithmName = "AES-CBC";
    const keyUsage = isEncrypting ? ["encrypt"] : ["encrypt", "decrypt"];
    const cryptoKey = await importKey(key, algorithmName, keyUsage);
    const algorithm = {
        name: algorithmName,
        iv: new Uint8Array(iv)
    };

    const result = await (isEncrypting ?
        crypto.subtle.encrypt(
            algorithm,
            cryptoKey,
            new Uint8Array(data)) :
        decrypt(
            algorithm,
            cryptoKey,
            data));

    let resultByteArray = new Uint8Array(result);
    if (isEncrypting) {
        // trim off the last block, which is always a padding block.
        resultByteArray = resultByteArray.slice(0, resultByteArray.length - AesBlockSizeBytes);
    }
    return Array.from(resultByteArray);
}

async function decrypt(algorithm, cryptoKey, data) {
    // crypto.subtle AES-CBC will only allow a PaddingMode of PKCS7, but we need to use
    // PaddingMode None. To simulate this, we only decrypt full blocks of data, with an extra full
    // padding block of 0x10 (16) bytes appended to data. crypto.subtle will see that padding block and return
    // the fully decrypted message. To create the encrypted padding block, we encrypt an empty array using the
    // last block of the cipher text as the IV. This will create a full block of padding bytes.

    const paddingBlockIV = new Uint8Array(data).slice(data.length - AesBlockSizeBytes);
    const empty = new Uint8Array();
    const encryptedPaddingBlockResult = await crypto.subtle.encrypt(
        {
            name: algorithm.name,
            iv: paddingBlockIV
        },
        cryptoKey,
        empty
    );

    const encryptedPaddingBlock = new Uint8Array(encryptedPaddingBlockResult);
    for (var i = 0; i < encryptedPaddingBlock.length; i++) {
        data.push(encryptedPaddingBlock[i]);
    }

    return await crypto.subtle.decrypt(
        algorithm,
        cryptoKey,
        new Uint8Array(data));
}

function importKey(key, algorithmName, keyUsage) {
    return crypto.subtle.importKey(
        "raw",
        new Uint8Array(key),
        {
            name: algorithmName
        },
        false /* extractable */,
        keyUsage);
}

// Operation to perform.
async function async_call(msg) {
    const req = JSON.parse(msg);

    if (req.func === "digest") {
        const digestArr = await call_digest(req.type, new Uint8Array(req.data));
        return JSON.stringify(digestArr);
    } 
    else if (req.func === "sign") {
        const signResult = await sign(req.type, new Uint8Array(req.key), new Uint8Array(req.data));
        return JSON.stringify(signResult);
    }
    else if (req.func === "encrypt_decrypt") {
        const signResult = await encrypt_decrypt(req.isEncrypting, req.key, req.iv, req.data);
        return JSON.stringify(signResult);
    }
    else {
        throw "CRYPTO: Unknown request: " + req.func;
    }
}

var s_channel;

// Initialize WebWorker
onmessage = function (p) {
    var data = p;
    if (p.data !== undefined) {
        data = p.data;
    }
    s_channel = ChannelWorker.create(data.comm_buf, data.msg_buf, data.msg_char_len);
    s_channel.await_request(async_call);
};