goscript 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compiler/gotest/runner.go +98 -0
- package/compiler/gotest/runner_test.go +45 -0
- package/compiler/gotest/testdata/browserapi/browserapi_test.go +36 -0
- package/compiler/lowering.go +227 -11
- package/compiler/override-registry_test.go +50 -0
- package/compiler/protobuf-ts-binding.go +155 -7
- package/compiler/protobuf-ts-binding_test.go +116 -2
- package/compiler/runtime-contract.go +2 -0
- package/compiler/runtime-contract_test.go +1 -0
- package/compiler/semantic-model.go +16 -0
- package/compiler/semantic-model_test.go +38 -0
- package/compiler/skeleton_test.go +477 -16
- package/compiler/typescript-emitter.go +4 -0
- package/dist/gs/builtin/builtin.js +7 -9
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/defer.js +2 -2
- package/dist/gs/builtin/hostio.js +5 -5
- package/dist/gs/builtin/hostio.js.map +1 -1
- package/dist/gs/builtin/map.js +2 -1
- package/dist/gs/builtin/map.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +3 -0
- package/dist/gs/builtin/slice.js +39 -0
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.js +49 -0
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/compress/zlib/index.js +5 -2
- package/dist/gs/compress/zlib/index.js.map +1 -1
- package/dist/gs/crypto/aes/index.d.ts +15 -0
- package/dist/gs/crypto/aes/index.js +57 -0
- package/dist/gs/crypto/aes/index.js.map +1 -0
- package/dist/gs/crypto/cipher/index.d.ts +41 -0
- package/dist/gs/crypto/cipher/index.js +255 -0
- package/dist/gs/crypto/cipher/index.js.map +1 -0
- package/dist/gs/crypto/ecdh/index.js +27 -8
- package/dist/gs/crypto/ecdh/index.js.map +1 -1
- package/dist/gs/crypto/ed25519/index.js +3 -3
- package/dist/gs/crypto/ed25519/index.js.map +1 -1
- package/dist/gs/crypto/rand/index.js +6 -3
- package/dist/gs/crypto/rand/index.js.map +1 -1
- package/dist/gs/embed/index.js +9 -3
- package/dist/gs/embed/index.js.map +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +33 -0
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
- package/dist/gs/github.com/mr-tron/base58/base58/index.js +4 -1
- package/dist/gs/github.com/mr-tron/base58/base58/index.js.map +1 -1
- package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.d.ts +31 -0
- package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js +117 -0
- package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js.map +1 -0
- package/dist/gs/golang.org/x/crypto/scrypt/index.d.ts +2 -0
- package/dist/gs/golang.org/x/crypto/scrypt/index.js +39 -0
- package/dist/gs/golang.org/x/crypto/scrypt/index.js.map +1 -0
- package/dist/gs/hash/fnv/index.js +13 -5
- package/dist/gs/hash/fnv/index.js.map +1 -1
- package/dist/gs/io/fs/glob.d.ts +3 -3
- package/dist/gs/io/fs/glob.js +8 -8
- package/dist/gs/io/fs/glob.js.map +1 -1
- package/dist/gs/io/fs/readdir.d.ts +2 -2
- package/dist/gs/io/fs/readdir.js +13 -74
- package/dist/gs/io/fs/readdir.js.map +1 -1
- package/dist/gs/io/fs/sub.js +4 -4
- package/dist/gs/io/fs/sub.js.map +1 -1
- package/dist/gs/io/fs/walk.js +1 -1
- package/dist/gs/io/fs/walk.js.map +1 -1
- package/dist/gs/io/io.js +18 -2
- package/dist/gs/io/io.js.map +1 -1
- package/dist/gs/maps/iter.js.map +1 -1
- package/dist/gs/maps/maps.js.map +1 -1
- package/dist/gs/mime/index.js +5 -2
- package/dist/gs/mime/index.js.map +1 -1
- package/dist/gs/net/http/httptest/index.js +6 -3
- package/dist/gs/net/http/httptest/index.js.map +1 -1
- package/dist/gs/net/http/index.d.ts +16 -4
- package/dist/gs/net/http/index.js +236 -40
- package/dist/gs/net/http/index.js.map +1 -1
- package/dist/gs/net/http/pprof/index.js.map +1 -1
- package/dist/gs/reflect/iter.js +1 -1
- package/dist/gs/reflect/iter.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +2 -0
- package/dist/gs/reflect/type.js +53 -21
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/runtime/debug/index.js +2 -1
- package/dist/gs/runtime/debug/index.js.map +1 -1
- package/dist/gs/runtime/pprof/index.js.map +1 -1
- package/dist/gs/runtime/runtime.js +2 -2
- package/dist/gs/runtime/runtime.js.map +1 -1
- package/dist/gs/runtime/trace/index.js.map +1 -1
- package/dist/gs/slices/slices.d.ts +1 -1
- package/dist/gs/slices/slices.js +37 -4
- package/dist/gs/slices/slices.js.map +1 -1
- package/go.mod +2 -2
- package/go.sum +2 -0
- package/gs/builtin/builtin.ts +11 -14
- package/gs/builtin/defer.ts +2 -2
- package/gs/builtin/hostio.test.ts +8 -3
- package/gs/builtin/hostio.ts +5 -7
- package/gs/builtin/map.ts +4 -1
- package/gs/builtin/slice.test.ts +14 -0
- package/gs/builtin/slice.ts +64 -0
- package/gs/builtin/type.ts +72 -0
- package/gs/bytes/bytes.test.ts +14 -13
- package/gs/compress/zlib/index.test.ts +19 -5
- package/gs/compress/zlib/index.ts +16 -7
- package/gs/context/context.test.ts +3 -1
- package/gs/crypto/aes/index.test.ts +120 -0
- package/gs/crypto/aes/index.ts +76 -0
- package/gs/crypto/cipher/index.ts +345 -0
- package/gs/crypto/cipher/meta.json +6 -0
- package/gs/crypto/ecdh/index.test.ts +6 -2
- package/gs/crypto/ecdh/index.ts +49 -12
- package/gs/crypto/ed25519/index.ts +20 -7
- package/gs/crypto/rand/index.ts +6 -3
- package/gs/embed/index.test.ts +3 -3
- package/gs/embed/index.ts +9 -3
- package/gs/fmt/fmt.test.ts +29 -4
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +126 -0
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +46 -0
- package/gs/github.com/mr-tron/base58/base58/index.ts +9 -3
- package/gs/github.com/zeebo/blake3/internal/consts/index.test.ts +2 -8
- package/gs/golang.org/x/crypto/chacha20poly1305/index.test.ts +91 -0
- package/gs/golang.org/x/crypto/chacha20poly1305/index.ts +245 -0
- package/gs/golang.org/x/crypto/scrypt/index.test.ts +81 -0
- package/gs/golang.org/x/crypto/scrypt/index.ts +54 -0
- package/gs/golang.org/x/crypto/scrypt/meta.json +5 -0
- package/gs/hash/fnv/index.test.ts +1 -8
- package/gs/hash/fnv/index.ts +27 -10
- package/gs/io/fs/glob.ts +13 -10
- package/gs/io/fs/meta.json +2 -0
- package/gs/io/fs/readdir.test.ts +63 -2
- package/gs/io/fs/readdir.ts +33 -30
- package/gs/io/fs/sub.ts +4 -4
- package/gs/io/fs/walk.ts +1 -1
- package/gs/io/io.test.ts +56 -1
- package/gs/io/io.ts +19 -2
- package/gs/maps/iter.ts +9 -9
- package/gs/maps/maps.ts +4 -4
- package/gs/math/bits/index.test.ts +10 -1
- package/gs/mime/index.test.ts +33 -15
- package/gs/mime/index.ts +9 -2
- package/gs/net/http/httptest/index.test.ts +17 -3
- package/gs/net/http/httptest/index.ts +8 -3
- package/gs/net/http/index.test.ts +645 -123
- package/gs/net/http/index.ts +548 -113
- package/gs/net/http/pprof/index.ts +24 -6
- package/gs/os/file_unix_js.test.ts +22 -0
- package/gs/reflect/iter.ts +4 -2
- package/gs/reflect/map.test.ts +56 -1
- package/gs/reflect/type.ts +76 -37
- package/gs/runtime/debug/index.test.ts +32 -4
- package/gs/runtime/debug/index.ts +5 -2
- package/gs/runtime/pprof/index.test.ts +7 -1
- package/gs/runtime/pprof/index.ts +5 -1
- package/gs/runtime/runtime.test.ts +7 -0
- package/gs/runtime/runtime.ts +2 -4
- package/gs/runtime/trace/index.test.ts +9 -1
- package/gs/runtime/trace/index.ts +5 -1
- package/gs/slices/meta.json +3 -0
- package/gs/slices/slices.test.ts +59 -21
- package/gs/slices/slices.ts +61 -20
- package/gs/strconv/complex.test.ts +17 -3
- package/gs/sync/atomic/doc_64.test.ts +2 -9
- package/gs/sync/sync.test.ts +18 -8
- package/gs/syscall/js/index.test.ts +9 -4
- package/package.json +13 -5
|
@@ -119,22 +119,36 @@ describe('compress/zlib override', () => {
|
|
|
119
119
|
test('writer and reader honor preset dictionaries', async () => {
|
|
120
120
|
const dict = $.stringToBytes('hello dictionary')
|
|
121
121
|
const compressed = $.markAsStructValue(new bytes.Buffer())
|
|
122
|
-
const [writer, writerErr] = NewWriterLevelDict(
|
|
122
|
+
const [writer, writerErr] = NewWriterLevelDict(
|
|
123
|
+
compressed,
|
|
124
|
+
DefaultCompression,
|
|
125
|
+
dict,
|
|
126
|
+
)
|
|
123
127
|
expect(writerErr).toBeNull()
|
|
124
|
-
expect(
|
|
128
|
+
expect(
|
|
129
|
+
writer!.Write($.stringToBytes('hello dictionary payload'))[1],
|
|
130
|
+
).toBeNull()
|
|
125
131
|
expect(await writer!.Close()).toBeNull()
|
|
126
132
|
|
|
127
|
-
const [missingDictReader, missingDictErr] = NewReader(
|
|
133
|
+
const [missingDictReader, missingDictErr] = NewReader(
|
|
134
|
+
bytes.NewReader(compressed.Bytes()),
|
|
135
|
+
)
|
|
128
136
|
expect(missingDictReader).toBeNull()
|
|
129
137
|
expect(missingDictErr).toBe(ErrDictionary)
|
|
130
138
|
|
|
131
|
-
const [reader, readerErr] = NewReaderDict(
|
|
139
|
+
const [reader, readerErr] = NewReaderDict(
|
|
140
|
+
bytes.NewReader(compressed.Bytes()),
|
|
141
|
+
dict,
|
|
142
|
+
)
|
|
132
143
|
expect(readerErr).toBeNull()
|
|
133
144
|
const [out, readErr] = await io.ReadAll(reader!)
|
|
134
145
|
expect(readErr).toBeNull()
|
|
135
146
|
expect($.bytesToString(out)).toBe('hello dictionary payload')
|
|
136
147
|
|
|
137
|
-
const [, wrongDictErr] = NewReaderDict(
|
|
148
|
+
const [, wrongDictErr] = NewReaderDict(
|
|
149
|
+
bytes.NewReader(compressed.Bytes()),
|
|
150
|
+
$.stringToBytes('wrong dictionary'),
|
|
151
|
+
)
|
|
138
152
|
expect(wrongDictErr).toBe(ErrDictionary)
|
|
139
153
|
|
|
140
154
|
const corrupt = Uint8Array.from(compressed.Bytes())
|
|
@@ -37,9 +37,8 @@ export function __goscript_set_ErrHeader(value: $.GoError): void {
|
|
|
37
37
|
class zlibReader implements Resetter {
|
|
38
38
|
private data: Uint8Array = new Uint8Array(0)
|
|
39
39
|
private offset = 0
|
|
40
|
-
private pending:
|
|
41
|
-
|
|
42
|
-
| null = null
|
|
40
|
+
private pending: Promise<{ data: Uint8Array | null; err: $.GoError }> | null =
|
|
41
|
+
null
|
|
43
42
|
|
|
44
43
|
constructor(data?: Uint8Array) {
|
|
45
44
|
if (data != null) {
|
|
@@ -180,7 +179,11 @@ export function NewReaderDict(
|
|
|
180
179
|
return [reader as any, null]
|
|
181
180
|
}
|
|
182
181
|
|
|
183
|
-
function deflate(
|
|
182
|
+
function deflate(
|
|
183
|
+
data: Uint8Array,
|
|
184
|
+
dict: $.Bytes | null,
|
|
185
|
+
level: number,
|
|
186
|
+
): Uint8Array {
|
|
184
187
|
const zlib = nodeZlib()
|
|
185
188
|
const opts: Record<string, unknown> = {}
|
|
186
189
|
if (dict != null && $.len(dict) > 0) {
|
|
@@ -197,7 +200,10 @@ function deflate(data: Uint8Array, dict: $.Bytes | null, level: number): Uint8Ar
|
|
|
197
200
|
|
|
198
201
|
function inflate(data: Uint8Array, dict: $.Bytes | null): Uint8Array {
|
|
199
202
|
const zlib = nodeZlib()
|
|
200
|
-
const opts =
|
|
203
|
+
const opts =
|
|
204
|
+
dict != null && $.len(dict) > 0 ?
|
|
205
|
+
{ dictionary: $.bytesToUint8Array(dict) }
|
|
206
|
+
: undefined
|
|
201
207
|
return new Uint8Array(zlib.inflateSync(data, opts))
|
|
202
208
|
}
|
|
203
209
|
|
|
@@ -302,11 +308,14 @@ function recordCompressedBytes(
|
|
|
302
308
|
}
|
|
303
309
|
|
|
304
310
|
function classifyInflateError(err: unknown): $.GoError {
|
|
305
|
-
const zerr = err instanceof Error ? err as nodeZlibError : null
|
|
311
|
+
const zerr = err instanceof Error ? (err as nodeZlibError) : null
|
|
306
312
|
if (zerr?.code === 'Z_NEED_DICT') {
|
|
307
313
|
return ErrDictionary
|
|
308
314
|
}
|
|
309
|
-
if (
|
|
315
|
+
if (
|
|
316
|
+
zerr?.code === 'Z_DATA_ERROR' &&
|
|
317
|
+
zerr.message.includes('incorrect data check')
|
|
318
|
+
) {
|
|
310
319
|
return ErrChecksum
|
|
311
320
|
}
|
|
312
321
|
return ErrHeader
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import * as $ from '@goscript/builtin/index.js'
|
|
4
|
+
import * as cipher from '@goscript/crypto/cipher/index.js'
|
|
5
|
+
|
|
6
|
+
import { BlockSize, KeySizeError_Error, NewCipher } from './index.js'
|
|
7
|
+
|
|
8
|
+
describe('crypto/aes WebCrypto override', () => {
|
|
9
|
+
test.each([
|
|
10
|
+
[
|
|
11
|
+
'AES-128',
|
|
12
|
+
'000102030405060708090a0b0c0d0e0f',
|
|
13
|
+
'00112233445566778899aabbccddeeff',
|
|
14
|
+
'69c4e0d86a7b0430d8cdb78070b4c55a',
|
|
15
|
+
],
|
|
16
|
+
[
|
|
17
|
+
'AES-192',
|
|
18
|
+
'000102030405060708090a0b0c0d0e0f1011121314151617',
|
|
19
|
+
'00112233445566778899aabbccddeeff',
|
|
20
|
+
'dda97ca4864cdfe06eaf70a0ec0d7191',
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
'AES-256',
|
|
24
|
+
'000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
|
|
25
|
+
'00112233445566778899aabbccddeeff',
|
|
26
|
+
'8ea2b7ca516745bfeafc49904b496089',
|
|
27
|
+
],
|
|
28
|
+
])(
|
|
29
|
+
'encrypts and decrypts a raw %s block',
|
|
30
|
+
(_name, key, plaintext, ciphertext) => {
|
|
31
|
+
const [block, err] = NewCipher(hex(key))
|
|
32
|
+
expect(err).toBeNull()
|
|
33
|
+
|
|
34
|
+
const encrypted = new Uint8Array(BlockSize)
|
|
35
|
+
block!.Encrypt(encrypted, hex(plaintext))
|
|
36
|
+
expect(toHex(encrypted)).toBe(ciphertext)
|
|
37
|
+
|
|
38
|
+
const decrypted = new Uint8Array(BlockSize)
|
|
39
|
+
block!.Decrypt(decrypted, encrypted)
|
|
40
|
+
expect(toHex(decrypted)).toBe(plaintext)
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
test('seals and opens the NIST AES-256-GCM vector', async () => {
|
|
45
|
+
const [block, blockErr] = NewCipher(
|
|
46
|
+
hex('0000000000000000000000000000000000000000000000000000000000000000'),
|
|
47
|
+
)
|
|
48
|
+
expect(blockErr).toBeNull()
|
|
49
|
+
expect(block?.BlockSize()).toBe(BlockSize)
|
|
50
|
+
|
|
51
|
+
const [aead, aeadErr] = cipher.NewGCM(block)
|
|
52
|
+
expect(aeadErr).toBeNull()
|
|
53
|
+
expect(aead?.NonceSize()).toBe(12)
|
|
54
|
+
expect(aead?.Overhead()).toBe(16)
|
|
55
|
+
|
|
56
|
+
const sealed = await aead!.Seal(
|
|
57
|
+
null,
|
|
58
|
+
hex('000000000000000000000000'),
|
|
59
|
+
hex('00000000000000000000000000000000'),
|
|
60
|
+
null,
|
|
61
|
+
)
|
|
62
|
+
expect(toHex(sealed)).toBe(
|
|
63
|
+
'cea7403d4d606b6e074ec5d3baf39d18d0d1c8a799996bf0265b98b5d48ab919',
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const [opened, openErr] = await aead!.Open(
|
|
67
|
+
$.stringToBytes('prefix:'),
|
|
68
|
+
hex('000000000000000000000000'),
|
|
69
|
+
sealed,
|
|
70
|
+
null,
|
|
71
|
+
)
|
|
72
|
+
expect(openErr).toBeNull()
|
|
73
|
+
expect(toHex(opened)).toBe(
|
|
74
|
+
toHex($.stringToBytes('prefix:')) + '00000000000000000000000000000000',
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('rejects tampered ciphertext', async () => {
|
|
79
|
+
const [block] = NewCipher(
|
|
80
|
+
hex('0000000000000000000000000000000000000000000000000000000000000000'),
|
|
81
|
+
)
|
|
82
|
+
const [aead] = cipher.NewGCM(block)
|
|
83
|
+
const sealed = await aead!.Seal(
|
|
84
|
+
null,
|
|
85
|
+
hex('000000000000000000000000'),
|
|
86
|
+
$.stringToBytes('hello'),
|
|
87
|
+
$.stringToBytes('aad'),
|
|
88
|
+
)
|
|
89
|
+
sealed![0] ^= 1
|
|
90
|
+
|
|
91
|
+
const [opened, openErr] = await aead!.Open(
|
|
92
|
+
null,
|
|
93
|
+
hex('000000000000000000000000'),
|
|
94
|
+
sealed,
|
|
95
|
+
$.stringToBytes('aad'),
|
|
96
|
+
)
|
|
97
|
+
expect(opened).toBeNull()
|
|
98
|
+
expect(openErr?.Error()).toBe('cipher: message authentication failed')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('rejects invalid key lengths', () => {
|
|
102
|
+
const [block, err] = NewCipher(new Uint8Array(31))
|
|
103
|
+
expect(block).toBeNull()
|
|
104
|
+
expect(err?.Error()).toBe(KeySizeError_Error(31))
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
function hex(input: string): Uint8Array {
|
|
109
|
+
const out = new Uint8Array(input.length / 2)
|
|
110
|
+
for (let idx = 0; idx < out.length; idx++) {
|
|
111
|
+
out[idx] = Number.parseInt(input.slice(idx * 2, idx * 2 + 2), 16)
|
|
112
|
+
}
|
|
113
|
+
return out
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function toHex(input: Uint8Array | number[] | null): string {
|
|
117
|
+
return Array.from(input ?? [])
|
|
118
|
+
.map((value) => value.toString(16).padStart(2, '0'))
|
|
119
|
+
.join('')
|
|
120
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as $ from '@goscript/builtin/index.js'
|
|
2
|
+
import type * as cipher from '@goscript/crypto/cipher/index.js'
|
|
3
|
+
import { ecb } from '@noble/ciphers/aes.js'
|
|
4
|
+
|
|
5
|
+
export const BlockSize = 16
|
|
6
|
+
|
|
7
|
+
export type KeySizeError = number
|
|
8
|
+
|
|
9
|
+
export class AESBlock implements cipher.Block {
|
|
10
|
+
private keyPromise: Promise<CryptoKey> | null = null
|
|
11
|
+
|
|
12
|
+
constructor(private readonly key: Uint8Array) {}
|
|
13
|
+
|
|
14
|
+
BlockSize(): number {
|
|
15
|
+
return BlockSize
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Encrypt(dst: $.Bytes, src: $.Bytes): void {
|
|
19
|
+
const srcBytes = $.bytesToUint8Array(src)
|
|
20
|
+
if (srcBytes.length < BlockSize) {
|
|
21
|
+
$.panic('crypto/aes: input not full block')
|
|
22
|
+
}
|
|
23
|
+
if ($.len(dst) < BlockSize) {
|
|
24
|
+
$.panic('crypto/aes: output not full block')
|
|
25
|
+
}
|
|
26
|
+
const out = ecb(this.key, { disablePadding: true }).encrypt(
|
|
27
|
+
srcBytes.subarray(0, BlockSize),
|
|
28
|
+
)
|
|
29
|
+
$.copy(dst as Uint8Array, out)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Decrypt(dst: $.Bytes, src: $.Bytes): void {
|
|
33
|
+
const srcBytes = $.bytesToUint8Array(src)
|
|
34
|
+
if (srcBytes.length < BlockSize) {
|
|
35
|
+
$.panic('crypto/aes: input not full block')
|
|
36
|
+
}
|
|
37
|
+
if ($.len(dst) < BlockSize) {
|
|
38
|
+
$.panic('crypto/aes: output not full block')
|
|
39
|
+
}
|
|
40
|
+
const out = ecb(this.key, { disablePadding: true }).decrypt(
|
|
41
|
+
srcBytes.subarray(0, BlockSize),
|
|
42
|
+
)
|
|
43
|
+
$.copy(dst as Uint8Array, out)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async webCryptoKey(): Promise<CryptoKey> {
|
|
47
|
+
this.keyPromise ??= subtleCrypto().importKey(
|
|
48
|
+
'raw',
|
|
49
|
+
this.key as unknown as BufferSource,
|
|
50
|
+
'AES-GCM',
|
|
51
|
+
false,
|
|
52
|
+
['encrypt', 'decrypt'],
|
|
53
|
+
)
|
|
54
|
+
return this.keyPromise
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function KeySizeError_Error(k: KeySizeError): string {
|
|
59
|
+
return `crypto/aes: invalid key size ${k}`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function NewCipher(key: $.Bytes): [cipher.Block | null, $.GoError] {
|
|
63
|
+
const k = $.len(key)
|
|
64
|
+
if (k !== 16 && k !== 24 && k !== 32) {
|
|
65
|
+
return [null, $.newError(KeySizeError_Error(k))]
|
|
66
|
+
}
|
|
67
|
+
return [new AESBlock($.bytesToUint8Array(key).slice()), null]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function subtleCrypto(): SubtleCrypto {
|
|
71
|
+
const subtle = globalThis.crypto?.subtle
|
|
72
|
+
if (subtle == null) {
|
|
73
|
+
throw new Error('crypto/aes: WebCrypto AES-GCM is unavailable')
|
|
74
|
+
}
|
|
75
|
+
return subtle
|
|
76
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import * as $ from '@goscript/builtin/index.js'
|
|
2
|
+
|
|
3
|
+
export type Block = {
|
|
4
|
+
BlockSize(): number
|
|
5
|
+
Decrypt(dst: $.Bytes, src: $.Bytes): void
|
|
6
|
+
Encrypt(dst: $.Bytes, src: $.Bytes): void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
$.registerInterfaceType('cipher.Block', null, [
|
|
10
|
+
{
|
|
11
|
+
name: 'BlockSize',
|
|
12
|
+
args: [],
|
|
13
|
+
returns: [{ name: '_r0', type: { kind: $.TypeKind.Basic, name: 'int' } }],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'Decrypt',
|
|
17
|
+
args: [
|
|
18
|
+
{
|
|
19
|
+
name: 'dst',
|
|
20
|
+
type: {
|
|
21
|
+
kind: $.TypeKind.Slice,
|
|
22
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'src',
|
|
27
|
+
type: {
|
|
28
|
+
kind: $.TypeKind.Slice,
|
|
29
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
returns: [],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'Encrypt',
|
|
37
|
+
args: [
|
|
38
|
+
{
|
|
39
|
+
name: 'dst',
|
|
40
|
+
type: {
|
|
41
|
+
kind: $.TypeKind.Slice,
|
|
42
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'src',
|
|
47
|
+
type: {
|
|
48
|
+
kind: $.TypeKind.Slice,
|
|
49
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
returns: [],
|
|
54
|
+
},
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
export type AEAD = {
|
|
58
|
+
NonceSize(): number
|
|
59
|
+
Open(
|
|
60
|
+
dst: $.Bytes,
|
|
61
|
+
nonce: $.Bytes,
|
|
62
|
+
ciphertext: $.Bytes,
|
|
63
|
+
additionalData: $.Bytes,
|
|
64
|
+
): [$.Bytes, $.GoError] | Promise<[$.Bytes, $.GoError]>
|
|
65
|
+
Overhead(): number
|
|
66
|
+
Seal(
|
|
67
|
+
dst: $.Bytes,
|
|
68
|
+
nonce: $.Bytes,
|
|
69
|
+
plaintext: $.Bytes,
|
|
70
|
+
additionalData: $.Bytes,
|
|
71
|
+
): $.Bytes | Promise<$.Bytes>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
$.registerInterfaceType('cipher.AEAD', null, [
|
|
75
|
+
{
|
|
76
|
+
name: 'NonceSize',
|
|
77
|
+
args: [],
|
|
78
|
+
returns: [{ name: '_r0', type: { kind: $.TypeKind.Basic, name: 'int' } }],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'Open',
|
|
82
|
+
args: [
|
|
83
|
+
{
|
|
84
|
+
name: 'dst',
|
|
85
|
+
type: {
|
|
86
|
+
kind: $.TypeKind.Slice,
|
|
87
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'nonce',
|
|
92
|
+
type: {
|
|
93
|
+
kind: $.TypeKind.Slice,
|
|
94
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'ciphertext',
|
|
99
|
+
type: {
|
|
100
|
+
kind: $.TypeKind.Slice,
|
|
101
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'additionalData',
|
|
106
|
+
type: {
|
|
107
|
+
kind: $.TypeKind.Slice,
|
|
108
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
returns: [
|
|
113
|
+
{
|
|
114
|
+
name: '_r0',
|
|
115
|
+
type: {
|
|
116
|
+
kind: $.TypeKind.Slice,
|
|
117
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{ name: '_r1', type: 'error' },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'Overhead',
|
|
125
|
+
args: [],
|
|
126
|
+
returns: [{ name: '_r0', type: { kind: $.TypeKind.Basic, name: 'int' } }],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'Seal',
|
|
130
|
+
args: [
|
|
131
|
+
{
|
|
132
|
+
name: 'dst',
|
|
133
|
+
type: {
|
|
134
|
+
kind: $.TypeKind.Slice,
|
|
135
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'nonce',
|
|
140
|
+
type: {
|
|
141
|
+
kind: $.TypeKind.Slice,
|
|
142
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'plaintext',
|
|
147
|
+
type: {
|
|
148
|
+
kind: $.TypeKind.Slice,
|
|
149
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'additionalData',
|
|
154
|
+
type: {
|
|
155
|
+
kind: $.TypeKind.Slice,
|
|
156
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
returns: [
|
|
161
|
+
{
|
|
162
|
+
name: '_r0',
|
|
163
|
+
type: {
|
|
164
|
+
kind: $.TypeKind.Slice,
|
|
165
|
+
elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
])
|
|
171
|
+
|
|
172
|
+
export type Stream = {
|
|
173
|
+
XORKeyStream(dst: $.Bytes, src: $.Bytes): void
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export type BlockMode = {
|
|
177
|
+
BlockSize(): number
|
|
178
|
+
CryptBlocks(dst: $.Bytes, src: $.Bytes): void
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
type WebCryptoBlock = Block & {
|
|
182
|
+
webCryptoKey(): Promise<CryptoKey>
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
class webCryptoGCM implements AEAD {
|
|
186
|
+
constructor(
|
|
187
|
+
private readonly block: WebCryptoBlock,
|
|
188
|
+
private readonly nonceSize: number,
|
|
189
|
+
private readonly tagSize: number,
|
|
190
|
+
) {}
|
|
191
|
+
|
|
192
|
+
NonceSize(): number {
|
|
193
|
+
return this.nonceSize
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
Overhead(): number {
|
|
197
|
+
return this.tagSize
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async Seal(
|
|
201
|
+
dst: $.Bytes,
|
|
202
|
+
nonce: $.Bytes,
|
|
203
|
+
plaintext: $.Bytes,
|
|
204
|
+
additionalData: $.Bytes,
|
|
205
|
+
): Promise<$.Bytes> {
|
|
206
|
+
if ($.len(nonce) !== this.nonceSize) {
|
|
207
|
+
throw new Error('crypto/cipher: incorrect nonce length given to GCM')
|
|
208
|
+
}
|
|
209
|
+
const encrypted = await globalThis.crypto.subtle.encrypt(
|
|
210
|
+
this.algorithm(nonce, additionalData),
|
|
211
|
+
await this.block.webCryptoKey(),
|
|
212
|
+
$.bytesToUint8Array(plaintext) as unknown as BufferSource,
|
|
213
|
+
)
|
|
214
|
+
return appendBytes(dst, new Uint8Array(encrypted))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async Open(
|
|
218
|
+
dst: $.Bytes,
|
|
219
|
+
nonce: $.Bytes,
|
|
220
|
+
ciphertext: $.Bytes,
|
|
221
|
+
additionalData: $.Bytes,
|
|
222
|
+
): Promise<[$.Bytes, $.GoError]> {
|
|
223
|
+
if ($.len(nonce) !== this.nonceSize) {
|
|
224
|
+
throw new Error('crypto/cipher: incorrect nonce length given to GCM')
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
const decrypted = await globalThis.crypto.subtle.decrypt(
|
|
228
|
+
this.algorithm(nonce, additionalData),
|
|
229
|
+
await this.block.webCryptoKey(),
|
|
230
|
+
$.bytesToUint8Array(ciphertext) as unknown as BufferSource,
|
|
231
|
+
)
|
|
232
|
+
return [appendBytes(dst, new Uint8Array(decrypted)), null]
|
|
233
|
+
} catch {
|
|
234
|
+
return [null, $.newError('cipher: message authentication failed')]
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private algorithm(nonce: $.Bytes, additionalData: $.Bytes): AesGcmParams {
|
|
239
|
+
const params: AesGcmParams = {
|
|
240
|
+
name: 'AES-GCM',
|
|
241
|
+
iv: $.bytesToUint8Array(nonce) as unknown as BufferSource,
|
|
242
|
+
tagLength: this.tagSize * 8,
|
|
243
|
+
}
|
|
244
|
+
if ($.len(additionalData) !== 0) {
|
|
245
|
+
params.additionalData = $.bytesToUint8Array(
|
|
246
|
+
additionalData,
|
|
247
|
+
) as unknown as BufferSource
|
|
248
|
+
}
|
|
249
|
+
return params
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function NewGCM(block: Block | null): [AEAD | null, $.GoError] {
|
|
254
|
+
return NewGCMWithNonceSize(block, 12)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function NewGCMWithNonceSize(
|
|
258
|
+
block: Block | null,
|
|
259
|
+
size: number,
|
|
260
|
+
): [AEAD | null, $.GoError] {
|
|
261
|
+
return newGCM(block, size, 16)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function NewGCMWithTagSize(
|
|
265
|
+
block: Block | null,
|
|
266
|
+
tagSize: number,
|
|
267
|
+
): [AEAD | null, $.GoError] {
|
|
268
|
+
if (tagSize < 12 || tagSize > 16) {
|
|
269
|
+
return [null, $.newError('crypto/cipher: incorrect GCM tag size')]
|
|
270
|
+
}
|
|
271
|
+
return newGCM(block, 12, tagSize)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function NewGCMWithRandomNonce(
|
|
275
|
+
_block: Block | null,
|
|
276
|
+
): [AEAD | null, $.GoError] {
|
|
277
|
+
return [
|
|
278
|
+
null,
|
|
279
|
+
$.newError(
|
|
280
|
+
'crypto/cipher: NewGCMWithRandomNonce is not implemented in GoScript',
|
|
281
|
+
),
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function NewCBCDecrypter(_b: Block | null, _iv: $.Bytes): BlockMode {
|
|
286
|
+
throw new Error('crypto/cipher: CBC is not implemented in GoScript')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function NewCBCEncrypter(_b: Block | null, _iv: $.Bytes): BlockMode {
|
|
290
|
+
throw new Error('crypto/cipher: CBC is not implemented in GoScript')
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function NewCFBDecrypter(_b: Block | null, _iv: $.Bytes): Stream {
|
|
294
|
+
throw new Error('crypto/cipher: CFB is not implemented in GoScript')
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function NewCFBEncrypter(_b: Block | null, _iv: $.Bytes): Stream {
|
|
298
|
+
throw new Error('crypto/cipher: CFB is not implemented in GoScript')
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function NewCTR(_b: Block | null, _iv: $.Bytes): Stream {
|
|
302
|
+
throw new Error('crypto/cipher: CTR is not implemented in GoScript')
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function NewOFB(_b: Block | null, _iv: $.Bytes): Stream {
|
|
306
|
+
throw new Error('crypto/cipher: OFB is not implemented in GoScript')
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export class StreamReader {
|
|
310
|
+
constructor(_init?: Partial<{ S: Stream; R: unknown }>) {}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export class StreamWriter {
|
|
314
|
+
constructor(_init?: Partial<{ S: Stream; W: unknown }>) {}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function newGCM(
|
|
318
|
+
block: Block | null,
|
|
319
|
+
nonceSize: number,
|
|
320
|
+
tagSize: number,
|
|
321
|
+
): [AEAD | null, $.GoError] {
|
|
322
|
+
if (block == null || block.BlockSize() !== 16) {
|
|
323
|
+
return [null, $.newError('cipher: NewGCM requires 128-bit block cipher')]
|
|
324
|
+
}
|
|
325
|
+
if (nonceSize <= 0) {
|
|
326
|
+
return [null, $.newError('crypto/cipher: incorrect GCM nonce size')]
|
|
327
|
+
}
|
|
328
|
+
if (!isWebCryptoBlock(block)) {
|
|
329
|
+
return [
|
|
330
|
+
null,
|
|
331
|
+
$.newError(
|
|
332
|
+
'crypto/cipher: AES-GCM requires a WebCrypto AES block in GoScript',
|
|
333
|
+
),
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
return [new webCryptoGCM(block, nonceSize, tagSize), null]
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function isWebCryptoBlock(block: Block): block is WebCryptoBlock {
|
|
340
|
+
return typeof (block as Partial<WebCryptoBlock>).webCryptoKey === 'function'
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function appendBytes(dst: $.Bytes, bytes: Uint8Array): $.Bytes {
|
|
344
|
+
return $.append(dst as any, ...bytes) as $.Bytes
|
|
345
|
+
}
|
|
@@ -35,9 +35,13 @@ describe('crypto/ecdh override', () => {
|
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
function hex(value: string): Uint8Array {
|
|
38
|
-
return new Uint8Array(
|
|
38
|
+
return new Uint8Array(
|
|
39
|
+
value.match(/../g)!.map((byte) => Number.parseInt(byte, 16)),
|
|
40
|
+
)
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
function toHex(value: Uint8Array | null): string {
|
|
42
|
-
return Array.from(value ?? [], (byte) =>
|
|
44
|
+
return Array.from(value ?? [], (byte) =>
|
|
45
|
+
byte.toString(16).padStart(2, '0'),
|
|
46
|
+
).join('')
|
|
43
47
|
}
|