@yielded/crypto 0.0.0-stage → 0.1.0-beta.19
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/LICENSE +21 -0
- package/README.md +39 -2
- package/THIRD_PARTY_NOTICES.md +378 -0
- package/dist/Aead.d.mts +34 -0
- package/dist/Aead.mjs +28 -0
- package/dist/Aead.mjs.map +1 -0
- package/dist/Errors.d.mts +20 -0
- package/dist/Errors.mjs +19 -0
- package/dist/Errors.mjs.map +1 -0
- package/dist/Hmac.d.mts +39 -0
- package/dist/Hmac.mjs +26 -0
- package/dist/Hmac.mjs.map +1 -0
- package/dist/Kdf.d.mts +54 -0
- package/dist/Kdf.mjs +78 -0
- package/dist/Kdf.mjs.map +1 -0
- package/dist/KdfAdmission.d.mts +24 -0
- package/dist/KdfAdmission.mjs +48 -0
- package/dist/KdfAdmission.mjs.map +1 -0
- package/dist/Portable.d.mts +11 -0
- package/dist/Portable.mjs +13 -0
- package/dist/Portable.mjs.map +1 -0
- package/dist/Signature.d.mts +70 -0
- package/dist/Signature.mjs +73 -0
- package/dist/Signature.mjs.map +1 -0
- package/dist/WebCrypto.d.mts +20 -0
- package/dist/WebCrypto.mjs +33 -0
- package/dist/WebCrypto.mjs.map +1 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +7 -0
- package/dist/internal/aead.mjs +81 -0
- package/dist/internal/aead.mjs.map +1 -0
- package/dist/internal/argon2-core.mjs +293 -0
- package/dist/internal/argon2-core.mjs.map +1 -0
- package/dist/internal/argon2.mjs +16 -0
- package/dist/internal/argon2.mjs.map +1 -0
- package/dist/internal/blake2b.mjs +359 -0
- package/dist/internal/blake2b.mjs.map +1 -0
- package/dist/internal/common.mjs +20 -0
- package/dist/internal/common.mjs.map +1 -0
- package/dist/internal/hmac.mjs +68 -0
- package/dist/internal/hmac.mjs.map +1 -0
- package/dist/internal/kdf.mjs +99 -0
- package/dist/internal/kdf.mjs.map +1 -0
- package/dist/internal/layer.mjs +26 -0
- package/dist/internal/layer.mjs.map +1 -0
- package/dist/internal/node.d.mts +11 -0
- package/dist/internal/node.mjs +34 -0
- package/dist/internal/node.mjs.map +1 -0
- package/dist/internal/poly1305.mjs +247 -0
- package/dist/internal/poly1305.mjs.map +1 -0
- package/dist/internal/signature.mjs +140 -0
- package/dist/internal/signature.mjs.map +1 -0
- package/dist/internal/word64.mjs +25 -0
- package/dist/internal/word64.mjs.map +1 -0
- package/dist/internal/xchacha-core.mjs +285 -0
- package/dist/internal/xchacha-core.mjs.map +1 -0
- package/dist/internal/xchacha.mjs +21 -0
- package/dist/internal/xchacha.mjs.map +1 -0
- package/dist/platform-bun.d.mts +2 -0
- package/dist/platform-bun.mjs +2 -0
- package/dist/platform-node.d.mts +2 -0
- package/dist/platform-node.mjs +2 -0
- package/package.json +1 -6
- package/src/Aead.ts +39 -0
- package/src/Errors.ts +27 -0
- package/src/Hmac.ts +41 -0
- package/src/Kdf.ts +87 -0
- package/src/KdfAdmission.ts +87 -0
- package/src/Portable.ts +8 -0
- package/src/Signature.ts +87 -0
- package/src/WebCrypto.ts +46 -0
- package/src/index.ts +6 -0
- package/src/internal/aead.ts +119 -0
- package/src/internal/argon2-core.ts +470 -0
- package/src/internal/argon2.ts +23 -0
- package/src/internal/blake2b.ts +230 -0
- package/src/internal/common.ts +44 -0
- package/src/internal/hmac.ts +111 -0
- package/src/internal/kdf.ts +150 -0
- package/src/internal/layer.ts +32 -0
- package/src/internal/node.ts +46 -0
- package/src/internal/poly1305.ts +289 -0
- package/src/internal/signature.ts +188 -0
- package/src/internal/word64.ts +48 -0
- package/src/internal/xchacha-core.ts +397 -0
- package/src/internal/xchacha.ts +26 -0
- package/src/platform-bun.ts +2 -0
- package/src/platform-node.ts +2 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Effect, Predicate, Redacted, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
import { CryptoUnavailable, InvalidInput, UnsupportedAlgorithm } from "../Errors";
|
|
4
|
+
|
|
5
|
+
export const decode = <A>(schema: Schema.Codec<A, A>, input: A, reason: InvalidInput["reason"]) =>
|
|
6
|
+
// Erased Redacted values can defect during Schema decoding. Keep decoder
|
|
7
|
+
// construction and evaluation inside the typed, secret-safe input boundary.
|
|
8
|
+
Effect.suspend(() => Schema.decodeEffect(schema)(input)).pipe(
|
|
9
|
+
Effect.mapError(() => InvalidInput.make({ reason })),
|
|
10
|
+
Effect.catchDefect(() => InvalidInput.make({ reason })),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export const nativeErrorName = (cause: unknown): string | undefined =>
|
|
14
|
+
Predicate.hasProperty(cause, "name") && Predicate.isString(cause.name) ? cause.name : undefined;
|
|
15
|
+
|
|
16
|
+
export const nativeError = (cause: unknown) =>
|
|
17
|
+
nativeErrorName(cause) === "NotSupportedError"
|
|
18
|
+
? UnsupportedAlgorithm.make({})
|
|
19
|
+
: CryptoUnavailable.make({});
|
|
20
|
+
|
|
21
|
+
export const importError = (cause: unknown) =>
|
|
22
|
+
nativeErrorName(cause) === "DataError" || nativeErrorName(cause) === "SyntaxError"
|
|
23
|
+
? InvalidInput.make({ reason: "key" })
|
|
24
|
+
: nativeError(cause);
|
|
25
|
+
|
|
26
|
+
/** Own a secret copy through cleanup. Native users must join work that cannot be cancelled. */
|
|
27
|
+
export const withSecret = <A, E, R>(
|
|
28
|
+
value: Redacted.Redacted<Uint8Array>,
|
|
29
|
+
use: (owned: Uint8Array<ArrayBuffer>) => Effect.Effect<A, E, R>,
|
|
30
|
+
) =>
|
|
31
|
+
Effect.acquireUseRelease(
|
|
32
|
+
Effect.try({
|
|
33
|
+
try: () => new Uint8Array(Redacted.value(value)),
|
|
34
|
+
catch: () => InvalidInput.make({ reason: "data" }),
|
|
35
|
+
}),
|
|
36
|
+
use,
|
|
37
|
+
(owned) => Effect.sync(() => owned.fill(0)),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export const copy = (value: Uint8Array) =>
|
|
41
|
+
Effect.try({
|
|
42
|
+
try: () => new Uint8Array(value),
|
|
43
|
+
catch: () => InvalidInput.make({ reason: "data" }),
|
|
44
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Effect, Exit, Fiber, Redacted, Schema, Scope } from "effect";
|
|
2
|
+
|
|
3
|
+
import { CryptoUnavailable } from "../Errors";
|
|
4
|
+
import { Hmac, Input, type Key, KeyInput, VerifyInput } from "../Hmac";
|
|
5
|
+
import { copy, decode, importError, nativeError, withSecret } from "./common";
|
|
6
|
+
|
|
7
|
+
const keyBytes = Schema.Uint8Array.check(Schema.isMinLength(1));
|
|
8
|
+
|
|
9
|
+
export const makeHmac = (subtle: SubtleCrypto): Hmac["Service"] => {
|
|
10
|
+
const importKey = Effect.fnUntraced(function* (input: KeyInput) {
|
|
11
|
+
const value = yield* decode(KeyInput, input, "key");
|
|
12
|
+
|
|
13
|
+
yield* decode(keyBytes, Redacted.value(value.key), "key");
|
|
14
|
+
|
|
15
|
+
// A sequential child joins every native call before dropping the key, even
|
|
16
|
+
// when the caller's Scope runs its other finalizers in parallel.
|
|
17
|
+
const scope = yield* Scope.fork(yield* Effect.scope, "sequential");
|
|
18
|
+
let imported: CryptoKey | undefined;
|
|
19
|
+
|
|
20
|
+
yield* Scope.addFinalizer(
|
|
21
|
+
scope,
|
|
22
|
+
Effect.sync(() => {
|
|
23
|
+
imported = undefined;
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const available = Effect.suspend(() =>
|
|
28
|
+
scope.state._tag === "Closed" ? Effect.fail(CryptoUnavailable.make({})) : Effect.void,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const run = <A, E>(operation: Effect.Effect<A, E>) =>
|
|
32
|
+
Effect.acquireUseRelease(
|
|
33
|
+
available.pipe(Effect.andThen(Effect.forkIn(operation, scope, { uninterruptible: false }))),
|
|
34
|
+
(fiber) =>
|
|
35
|
+
Effect.gen(function* () {
|
|
36
|
+
const exit = yield* Fiber.await(fiber);
|
|
37
|
+
|
|
38
|
+
yield* available;
|
|
39
|
+
|
|
40
|
+
return yield* exit;
|
|
41
|
+
}),
|
|
42
|
+
Fiber.interrupt,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
yield* run(
|
|
46
|
+
withSecret(value.key, (material) =>
|
|
47
|
+
Effect.tryPromise({
|
|
48
|
+
try: () =>
|
|
49
|
+
subtle.importKey("raw", material, { name: "HMAC", hash: value.algorithm }, false, [
|
|
50
|
+
"sign",
|
|
51
|
+
"verify",
|
|
52
|
+
]),
|
|
53
|
+
catch: importError,
|
|
54
|
+
}).pipe(
|
|
55
|
+
Effect.tap((key) =>
|
|
56
|
+
Effect.sync(() => {
|
|
57
|
+
imported = key;
|
|
58
|
+
}),
|
|
59
|
+
),
|
|
60
|
+
Effect.uninterruptible,
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
).pipe(
|
|
64
|
+
Effect.onExit((exit) => (Exit.isFailure(exit) ? Scope.close(scope, exit) : Effect.void)),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const use = <A>(operation: (key: CryptoKey) => Promise<A>) =>
|
|
68
|
+
run(
|
|
69
|
+
Effect.suspend(() => {
|
|
70
|
+
const key = imported;
|
|
71
|
+
|
|
72
|
+
return key === undefined
|
|
73
|
+
? Effect.fail(CryptoUnavailable.make({}))
|
|
74
|
+
: Effect.tryPromise({ try: () => operation(key), catch: nativeError });
|
|
75
|
+
}).pipe(Effect.uninterruptible),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
sign: Effect.fnUntraced(function* (input) {
|
|
80
|
+
const data = yield* decode(Schema.Uint8Array, input, "data").pipe(Effect.flatMap(copy));
|
|
81
|
+
|
|
82
|
+
return new Uint8Array(yield* use((key) => subtle.sign("HMAC", key, data)));
|
|
83
|
+
}),
|
|
84
|
+
verify: Effect.fnUntraced(function* (input, signature) {
|
|
85
|
+
const data = yield* decode(Schema.Uint8Array, input, "data").pipe(Effect.flatMap(copy));
|
|
86
|
+
const tag = yield* decode(Schema.Uint8Array, signature, "data").pipe(Effect.flatMap(copy));
|
|
87
|
+
|
|
88
|
+
return yield* use((key) => subtle.verify("HMAC", key, tag, data));
|
|
89
|
+
}),
|
|
90
|
+
} satisfies Key;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return Hmac.of({
|
|
94
|
+
importKey,
|
|
95
|
+
sign: Effect.fnUntraced(function* (input) {
|
|
96
|
+
const value = yield* decode(Input, input, "data");
|
|
97
|
+
const data = yield* copy(value.data);
|
|
98
|
+
const key = yield* importKey(value);
|
|
99
|
+
|
|
100
|
+
return yield* key.sign(data);
|
|
101
|
+
}, Effect.scoped),
|
|
102
|
+
verify: Effect.fnUntraced(function* (input) {
|
|
103
|
+
const value = yield* decode(VerifyInput, input, "data");
|
|
104
|
+
const data = yield* copy(value.data);
|
|
105
|
+
const tag = yield* copy(value.tag);
|
|
106
|
+
const key = yield* importKey(value);
|
|
107
|
+
|
|
108
|
+
return yield* key.verify(data, tag);
|
|
109
|
+
}, Effect.scoped),
|
|
110
|
+
});
|
|
111
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Effect, Redacted, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
import { InvalidInput, type OperationError, UnsupportedAlgorithm } from "../Errors";
|
|
4
|
+
import { Argon2idInput, HkdfInput, Kdf, type Limits, Pbkdf2Input } from "../Kdf";
|
|
5
|
+
import { KdfAdmission } from "../KdfAdmission";
|
|
6
|
+
import { copy, decode, importError, nativeError, withSecret } from "./common";
|
|
7
|
+
|
|
8
|
+
export interface Argon2Parameters {
|
|
9
|
+
readonly password: Uint8Array<ArrayBuffer>;
|
|
10
|
+
readonly salt: Uint8Array<ArrayBuffer>;
|
|
11
|
+
readonly memoryKiB: number;
|
|
12
|
+
readonly passes: number;
|
|
13
|
+
readonly parallelism: number;
|
|
14
|
+
readonly length: number;
|
|
15
|
+
readonly secret?: Uint8Array<ArrayBuffer>;
|
|
16
|
+
readonly associatedData: Uint8Array<ArrayBuffer>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type Argon2 = (input: Argon2Parameters) => Effect.Effect<Uint8Array, OperationError>;
|
|
20
|
+
|
|
21
|
+
export const makeKdf = Effect.fnUntraced(function* (
|
|
22
|
+
subtle: SubtleCrypto,
|
|
23
|
+
limits: Limits,
|
|
24
|
+
argon2?: Argon2,
|
|
25
|
+
) {
|
|
26
|
+
const admission = yield* KdfAdmission;
|
|
27
|
+
const boundedBytes = Schema.Uint8Array.check(Schema.isMaxLength(limits.maximumInputBytes));
|
|
28
|
+
|
|
29
|
+
const output = Schema.Int.check(
|
|
30
|
+
Schema.isBetween({ minimum: 1, maximum: limits.maximumOutputBytes }),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const inputBytes = (input: Uint8Array) => decode(boundedBytes, input, "data");
|
|
34
|
+
const outputLength = (input: number) => decode(output, input, "parameters");
|
|
35
|
+
|
|
36
|
+
return Kdf.of({
|
|
37
|
+
pbkdf2: (input) =>
|
|
38
|
+
admission.run(
|
|
39
|
+
Effect.gen(function* () {
|
|
40
|
+
const value = yield* decode(Pbkdf2Input, input, "parameters");
|
|
41
|
+
|
|
42
|
+
yield* decode(
|
|
43
|
+
Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: limits.maximumIterations })),
|
|
44
|
+
value.iterations,
|
|
45
|
+
"parameters",
|
|
46
|
+
);
|
|
47
|
+
yield* outputLength(value.length);
|
|
48
|
+
yield* inputBytes(Redacted.value(value.password));
|
|
49
|
+
yield* inputBytes(value.salt);
|
|
50
|
+
const salt = yield* copy(value.salt);
|
|
51
|
+
|
|
52
|
+
return yield* withSecret(value.password, (password) =>
|
|
53
|
+
Effect.gen(function* () {
|
|
54
|
+
const key = yield* Effect.tryPromise({
|
|
55
|
+
try: () => subtle.importKey("raw", password, "PBKDF2", false, ["deriveBits"]),
|
|
56
|
+
catch: importError,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const result = yield* Effect.tryPromise({
|
|
60
|
+
try: () =>
|
|
61
|
+
subtle.deriveBits(
|
|
62
|
+
{ name: "PBKDF2", hash: "SHA-256", salt, iterations: value.iterations },
|
|
63
|
+
key,
|
|
64
|
+
value.length * 8,
|
|
65
|
+
),
|
|
66
|
+
catch: nativeError,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return Redacted.make(new Uint8Array(result));
|
|
70
|
+
}).pipe(Effect.uninterruptible),
|
|
71
|
+
);
|
|
72
|
+
}),
|
|
73
|
+
),
|
|
74
|
+
hkdf: (input) =>
|
|
75
|
+
admission.run(
|
|
76
|
+
Effect.gen(function* () {
|
|
77
|
+
const value = yield* decode(HkdfInput, input, "parameters");
|
|
78
|
+
|
|
79
|
+
yield* outputLength(value.length);
|
|
80
|
+
yield* inputBytes(Redacted.value(value.key));
|
|
81
|
+
yield* inputBytes(value.salt);
|
|
82
|
+
yield* inputBytes(value.info);
|
|
83
|
+
const salt = yield* copy(value.salt);
|
|
84
|
+
const info = yield* copy(value.info);
|
|
85
|
+
|
|
86
|
+
return yield* withSecret(value.key, (material) =>
|
|
87
|
+
Effect.gen(function* () {
|
|
88
|
+
const key = yield* Effect.tryPromise({
|
|
89
|
+
try: () => subtle.importKey("raw", material, "HKDF", false, ["deriveBits"]),
|
|
90
|
+
catch: importError,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const result = yield* Effect.tryPromise({
|
|
94
|
+
try: () =>
|
|
95
|
+
subtle.deriveBits(
|
|
96
|
+
{ name: "HKDF", hash: "SHA-256", salt, info },
|
|
97
|
+
key,
|
|
98
|
+
value.length * 8,
|
|
99
|
+
),
|
|
100
|
+
catch: nativeError,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return Redacted.make(new Uint8Array(result));
|
|
104
|
+
}).pipe(Effect.uninterruptible),
|
|
105
|
+
);
|
|
106
|
+
}),
|
|
107
|
+
),
|
|
108
|
+
argon2id: (input) =>
|
|
109
|
+
admission.run(
|
|
110
|
+
Effect.gen(function* () {
|
|
111
|
+
const value = yield* decode(Argon2idInput, input, "parameters");
|
|
112
|
+
|
|
113
|
+
yield* outputLength(value.length);
|
|
114
|
+
yield* inputBytes(Redacted.value(value.password));
|
|
115
|
+
yield* inputBytes(value.salt);
|
|
116
|
+
if (value.secret !== undefined) yield* inputBytes(Redacted.value(value.secret));
|
|
117
|
+
if (value.associatedData !== undefined) yield* inputBytes(value.associatedData);
|
|
118
|
+
|
|
119
|
+
if (
|
|
120
|
+
value.memoryKiB < 8 * value.parallelism ||
|
|
121
|
+
value.memoryKiB > limits.maximumMemoryKiB ||
|
|
122
|
+
value.passes > limits.maximumPasses ||
|
|
123
|
+
value.parallelism > limits.maximumParallelism ||
|
|
124
|
+
value.memoryKiB * value.passes > limits.maximumMemoryPasses
|
|
125
|
+
)
|
|
126
|
+
return yield* InvalidInput.make({ reason: "parameters" });
|
|
127
|
+
if (argon2 === undefined) return yield* UnsupportedAlgorithm.make({});
|
|
128
|
+
|
|
129
|
+
const salt = yield* copy(value.salt);
|
|
130
|
+
const associatedData = yield* copy(value.associatedData ?? new Uint8Array());
|
|
131
|
+
|
|
132
|
+
return yield* withSecret(value.password, (password) => {
|
|
133
|
+
const derive = (secret?: Uint8Array<ArrayBuffer>) =>
|
|
134
|
+
argon2({
|
|
135
|
+
password,
|
|
136
|
+
salt,
|
|
137
|
+
...(secret === undefined ? {} : { secret }),
|
|
138
|
+
associatedData,
|
|
139
|
+
memoryKiB: value.memoryKiB,
|
|
140
|
+
passes: value.passes,
|
|
141
|
+
parallelism: value.parallelism,
|
|
142
|
+
length: value.length,
|
|
143
|
+
}).pipe(Effect.map((bytes) => Redacted.make(bytes)));
|
|
144
|
+
|
|
145
|
+
return value.secret === undefined ? derive() : withSecret(value.secret, derive);
|
|
146
|
+
});
|
|
147
|
+
}),
|
|
148
|
+
),
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
|
|
3
|
+
import { Aead } from "../Aead";
|
|
4
|
+
import { Hmac } from "../Hmac";
|
|
5
|
+
import { defaultLimits, Kdf, Limits } from "../Kdf";
|
|
6
|
+
import { Signature } from "../Signature";
|
|
7
|
+
import { makeAead, type XChaCha } from "./aead";
|
|
8
|
+
import { decode } from "./common";
|
|
9
|
+
import { makeHmac } from "./hmac";
|
|
10
|
+
import { type Argon2, makeKdf } from "./kdf";
|
|
11
|
+
import { makeSignature } from "./signature";
|
|
12
|
+
|
|
13
|
+
export const makeLayer = (
|
|
14
|
+
subtle: SubtleCrypto,
|
|
15
|
+
input: Partial<Limits>,
|
|
16
|
+
extensions: { readonly argon2?: Argon2; readonly xchacha?: XChaCha } = {},
|
|
17
|
+
) => {
|
|
18
|
+
const snapshot = { ...defaultLimits, ...input };
|
|
19
|
+
|
|
20
|
+
return Layer.effectContext(
|
|
21
|
+
Effect.gen(function* () {
|
|
22
|
+
const limits = yield* decode(Limits, snapshot, "parameters");
|
|
23
|
+
const kdf = yield* makeKdf(subtle, limits, extensions.argon2);
|
|
24
|
+
|
|
25
|
+
return Context.make(Aead, makeAead(subtle, extensions.xchacha)).pipe(
|
|
26
|
+
Context.add(Hmac, makeHmac(subtle)),
|
|
27
|
+
Context.add(Kdf, kdf),
|
|
28
|
+
Context.add(Signature, makeSignature(subtle)),
|
|
29
|
+
);
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { Effect, Result } from "effect";
|
|
4
|
+
|
|
5
|
+
import { CryptoUnavailable, UnsupportedAlgorithm } from "../Errors";
|
|
6
|
+
import type { Limits } from "../Kdf";
|
|
7
|
+
import type { Argon2, Argon2Parameters } from "./kdf";
|
|
8
|
+
import { makeLayer } from "./layer";
|
|
9
|
+
import { xchacha } from "./xchacha";
|
|
10
|
+
|
|
11
|
+
const argon2: Argon2 = Effect.fnUntraced(function* (input: Argon2Parameters) {
|
|
12
|
+
// Namespace import keeps this module loadable on Node versions without Argon2.
|
|
13
|
+
if (typeof crypto.argon2 !== "function") return yield* UnsupportedAlgorithm.make({});
|
|
14
|
+
|
|
15
|
+
// Node cannot cancel this work. Keep borrowed buffers and admission until the
|
|
16
|
+
// callback completes, including when its caller has requested interruption.
|
|
17
|
+
return yield* Effect.callback<Uint8Array, CryptoUnavailable>((resume) => {
|
|
18
|
+
const registration = Result.try({
|
|
19
|
+
try: () =>
|
|
20
|
+
crypto.argon2(
|
|
21
|
+
"argon2id",
|
|
22
|
+
{
|
|
23
|
+
message: input.password,
|
|
24
|
+
nonce: input.salt,
|
|
25
|
+
parallelism: input.parallelism,
|
|
26
|
+
memory: input.memoryKiB,
|
|
27
|
+
passes: input.passes,
|
|
28
|
+
tagLength: input.length,
|
|
29
|
+
secret: input.secret,
|
|
30
|
+
associatedData: input.associatedData,
|
|
31
|
+
},
|
|
32
|
+
(error, key) =>
|
|
33
|
+
resume(error === null ? Effect.succeed(key) : Effect.fail(CryptoUnavailable.make({}))),
|
|
34
|
+
),
|
|
35
|
+
catch: () => CryptoUnavailable.make({}),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (Result.isFailure(registration)) resume(Effect.fail(registration.failure));
|
|
39
|
+
}).pipe(Effect.uninterruptible);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/** Node-compatible WebCrypto/Argon2id with the portable XChaCha20-Poly1305 extension. */
|
|
43
|
+
export const layer = (limits: Partial<Limits> = {}) =>
|
|
44
|
+
// Node's declarations include additional key usages absent from lib.dom. The
|
|
45
|
+
// standard operations used here share the WebCrypto ABI; no payload is cast.
|
|
46
|
+
makeLayer(crypto.webcrypto.subtle as SubtleCrypto, limits, { argon2, xchacha });
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from noble-ciphers, Copyright (c) 2022 Paul Miller.
|
|
3
|
+
* MIT; source revision 785181b0772ba84afbe812025714163e4e48262f.
|
|
4
|
+
* Poly1305 arithmetic derives from the public-domain poly1305-donna implementation.
|
|
5
|
+
* See ../../THIRD_PARTY_NOTICES.md for source mapping and licenses.
|
|
6
|
+
*/
|
|
7
|
+
function u8to16(a: Uint8Array, i: number) {
|
|
8
|
+
return (a[i++] & 0xff) | ((a[i++] & 0xff) << 8);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class Poly1305 {
|
|
12
|
+
readonly blockLen = 16;
|
|
13
|
+
readonly outputLen = 16;
|
|
14
|
+
private buffer = new Uint8Array(16);
|
|
15
|
+
private r = new Uint16Array(10);
|
|
16
|
+
private h = new Uint16Array(10);
|
|
17
|
+
private pad = new Uint16Array(8);
|
|
18
|
+
private pos = 0;
|
|
19
|
+
|
|
20
|
+
constructor(key: Uint8Array) {
|
|
21
|
+
const t0 = u8to16(key, 0);
|
|
22
|
+
const t1 = u8to16(key, 2);
|
|
23
|
+
const t2 = u8to16(key, 4);
|
|
24
|
+
const t3 = u8to16(key, 6);
|
|
25
|
+
const t4 = u8to16(key, 8);
|
|
26
|
+
const t5 = u8to16(key, 10);
|
|
27
|
+
const t6 = u8to16(key, 12);
|
|
28
|
+
const t7 = u8to16(key, 14);
|
|
29
|
+
|
|
30
|
+
// https://github.com/floodyberry/poly1305-donna/blob/e6ad6e091d30d7f4ec2d4f978be1fcfcbce72781/poly1305-donna-16.h#L47
|
|
31
|
+
this.r[0] = t0 & 0x1fff;
|
|
32
|
+
this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff;
|
|
33
|
+
this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03;
|
|
34
|
+
this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff;
|
|
35
|
+
this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff;
|
|
36
|
+
this.r[5] = (t4 >>> 1) & 0x1ffe;
|
|
37
|
+
this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff;
|
|
38
|
+
this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81;
|
|
39
|
+
this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff;
|
|
40
|
+
this.r[9] = (t7 >>> 5) & 0x007f;
|
|
41
|
+
for (let i = 0; i < 8; i++) this.pad[i] = u8to16(key, 16 + 2 * i);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private process(data: Uint8Array, offset: number, isLast = false) {
|
|
45
|
+
const hibit = isLast ? 0 : 1 << 11;
|
|
46
|
+
const { h, r } = this;
|
|
47
|
+
const r0 = r[0];
|
|
48
|
+
const r1 = r[1];
|
|
49
|
+
const r2 = r[2];
|
|
50
|
+
const r3 = r[3];
|
|
51
|
+
const r4 = r[4];
|
|
52
|
+
const r5 = r[5];
|
|
53
|
+
const r6 = r[6];
|
|
54
|
+
const r7 = r[7];
|
|
55
|
+
const r8 = r[8];
|
|
56
|
+
const r9 = r[9];
|
|
57
|
+
|
|
58
|
+
const t0 = u8to16(data, offset + 0);
|
|
59
|
+
const t1 = u8to16(data, offset + 2);
|
|
60
|
+
const t2 = u8to16(data, offset + 4);
|
|
61
|
+
const t3 = u8to16(data, offset + 6);
|
|
62
|
+
const t4 = u8to16(data, offset + 8);
|
|
63
|
+
const t5 = u8to16(data, offset + 10);
|
|
64
|
+
const t6 = u8to16(data, offset + 12);
|
|
65
|
+
const t7 = u8to16(data, offset + 14);
|
|
66
|
+
|
|
67
|
+
let h0 = h[0] + (t0 & 0x1fff);
|
|
68
|
+
let h1 = h[1] + (((t0 >>> 13) | (t1 << 3)) & 0x1fff);
|
|
69
|
+
let h2 = h[2] + (((t1 >>> 10) | (t2 << 6)) & 0x1fff);
|
|
70
|
+
let h3 = h[3] + (((t2 >>> 7) | (t3 << 9)) & 0x1fff);
|
|
71
|
+
let h4 = h[4] + (((t3 >>> 4) | (t4 << 12)) & 0x1fff);
|
|
72
|
+
let h5 = h[5] + ((t4 >>> 1) & 0x1fff);
|
|
73
|
+
let h6 = h[6] + (((t4 >>> 14) | (t5 << 2)) & 0x1fff);
|
|
74
|
+
let h7 = h[7] + (((t5 >>> 11) | (t6 << 5)) & 0x1fff);
|
|
75
|
+
let h8 = h[8] + (((t6 >>> 8) | (t7 << 8)) & 0x1fff);
|
|
76
|
+
let h9 = h[9] + ((t7 >>> 5) | hibit);
|
|
77
|
+
|
|
78
|
+
let c = 0;
|
|
79
|
+
|
|
80
|
+
let d0 = c + h0 * r0 + h1 * (5 * r9) + h2 * (5 * r8) + h3 * (5 * r7) + h4 * (5 * r6);
|
|
81
|
+
|
|
82
|
+
c = d0 >>> 13;
|
|
83
|
+
d0 &= 0x1fff;
|
|
84
|
+
d0 += h5 * (5 * r5) + h6 * (5 * r4) + h7 * (5 * r3) + h8 * (5 * r2) + h9 * (5 * r1);
|
|
85
|
+
c += d0 >>> 13;
|
|
86
|
+
d0 &= 0x1fff;
|
|
87
|
+
|
|
88
|
+
let d1 = c + h0 * r1 + h1 * r0 + h2 * (5 * r9) + h3 * (5 * r8) + h4 * (5 * r7);
|
|
89
|
+
|
|
90
|
+
c = d1 >>> 13;
|
|
91
|
+
d1 &= 0x1fff;
|
|
92
|
+
d1 += h5 * (5 * r6) + h6 * (5 * r5) + h7 * (5 * r4) + h8 * (5 * r3) + h9 * (5 * r2);
|
|
93
|
+
c += d1 >>> 13;
|
|
94
|
+
d1 &= 0x1fff;
|
|
95
|
+
|
|
96
|
+
let d2 = c + h0 * r2 + h1 * r1 + h2 * r0 + h3 * (5 * r9) + h4 * (5 * r8);
|
|
97
|
+
|
|
98
|
+
c = d2 >>> 13;
|
|
99
|
+
d2 &= 0x1fff;
|
|
100
|
+
d2 += h5 * (5 * r7) + h6 * (5 * r6) + h7 * (5 * r5) + h8 * (5 * r4) + h9 * (5 * r3);
|
|
101
|
+
c += d2 >>> 13;
|
|
102
|
+
d2 &= 0x1fff;
|
|
103
|
+
|
|
104
|
+
let d3 = c + h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * (5 * r9);
|
|
105
|
+
|
|
106
|
+
c = d3 >>> 13;
|
|
107
|
+
d3 &= 0x1fff;
|
|
108
|
+
d3 += h5 * (5 * r8) + h6 * (5 * r7) + h7 * (5 * r6) + h8 * (5 * r5) + h9 * (5 * r4);
|
|
109
|
+
c += d3 >>> 13;
|
|
110
|
+
d3 &= 0x1fff;
|
|
111
|
+
|
|
112
|
+
let d4 = c + h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0;
|
|
113
|
+
|
|
114
|
+
c = d4 >>> 13;
|
|
115
|
+
d4 &= 0x1fff;
|
|
116
|
+
d4 += h5 * (5 * r9) + h6 * (5 * r8) + h7 * (5 * r7) + h8 * (5 * r6) + h9 * (5 * r5);
|
|
117
|
+
c += d4 >>> 13;
|
|
118
|
+
d4 &= 0x1fff;
|
|
119
|
+
|
|
120
|
+
let d5 = c + h0 * r5 + h1 * r4 + h2 * r3 + h3 * r2 + h4 * r1;
|
|
121
|
+
|
|
122
|
+
c = d5 >>> 13;
|
|
123
|
+
d5 &= 0x1fff;
|
|
124
|
+
d5 += h5 * r0 + h6 * (5 * r9) + h7 * (5 * r8) + h8 * (5 * r7) + h9 * (5 * r6);
|
|
125
|
+
c += d5 >>> 13;
|
|
126
|
+
d5 &= 0x1fff;
|
|
127
|
+
|
|
128
|
+
let d6 = c + h0 * r6 + h1 * r5 + h2 * r4 + h3 * r3 + h4 * r2;
|
|
129
|
+
|
|
130
|
+
c = d6 >>> 13;
|
|
131
|
+
d6 &= 0x1fff;
|
|
132
|
+
d6 += h5 * r1 + h6 * r0 + h7 * (5 * r9) + h8 * (5 * r8) + h9 * (5 * r7);
|
|
133
|
+
c += d6 >>> 13;
|
|
134
|
+
d6 &= 0x1fff;
|
|
135
|
+
|
|
136
|
+
let d7 = c + h0 * r7 + h1 * r6 + h2 * r5 + h3 * r4 + h4 * r3;
|
|
137
|
+
|
|
138
|
+
c = d7 >>> 13;
|
|
139
|
+
d7 &= 0x1fff;
|
|
140
|
+
d7 += h5 * r2 + h6 * r1 + h7 * r0 + h8 * (5 * r9) + h9 * (5 * r8);
|
|
141
|
+
c += d7 >>> 13;
|
|
142
|
+
d7 &= 0x1fff;
|
|
143
|
+
|
|
144
|
+
let d8 = c + h0 * r8 + h1 * r7 + h2 * r6 + h3 * r5 + h4 * r4;
|
|
145
|
+
|
|
146
|
+
c = d8 >>> 13;
|
|
147
|
+
d8 &= 0x1fff;
|
|
148
|
+
d8 += h5 * r3 + h6 * r2 + h7 * r1 + h8 * r0 + h9 * (5 * r9);
|
|
149
|
+
c += d8 >>> 13;
|
|
150
|
+
d8 &= 0x1fff;
|
|
151
|
+
|
|
152
|
+
let d9 = c + h0 * r9 + h1 * r8 + h2 * r7 + h3 * r6 + h4 * r5;
|
|
153
|
+
|
|
154
|
+
c = d9 >>> 13;
|
|
155
|
+
d9 &= 0x1fff;
|
|
156
|
+
d9 += h5 * r4 + h6 * r3 + h7 * r2 + h8 * r1 + h9 * r0;
|
|
157
|
+
c += d9 >>> 13;
|
|
158
|
+
d9 &= 0x1fff;
|
|
159
|
+
|
|
160
|
+
c = ((c << 2) + c) | 0;
|
|
161
|
+
c = (c + d0) | 0;
|
|
162
|
+
d0 = c & 0x1fff;
|
|
163
|
+
c = c >>> 13;
|
|
164
|
+
d1 += c;
|
|
165
|
+
|
|
166
|
+
h[0] = d0;
|
|
167
|
+
h[1] = d1;
|
|
168
|
+
h[2] = d2;
|
|
169
|
+
h[3] = d3;
|
|
170
|
+
h[4] = d4;
|
|
171
|
+
h[5] = d5;
|
|
172
|
+
h[6] = d6;
|
|
173
|
+
h[7] = d7;
|
|
174
|
+
h[8] = d8;
|
|
175
|
+
h[9] = d9;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private finalize() {
|
|
179
|
+
const { h, pad } = this;
|
|
180
|
+
const g = new Uint16Array(10);
|
|
181
|
+
let c = h[1] >>> 13;
|
|
182
|
+
|
|
183
|
+
h[1] &= 0x1fff;
|
|
184
|
+
for (let i = 2; i < 10; i++) {
|
|
185
|
+
h[i] += c;
|
|
186
|
+
c = h[i] >>> 13;
|
|
187
|
+
h[i] &= 0x1fff;
|
|
188
|
+
}
|
|
189
|
+
h[0] += c * 5;
|
|
190
|
+
c = h[0] >>> 13;
|
|
191
|
+
h[0] &= 0x1fff;
|
|
192
|
+
h[1] += c;
|
|
193
|
+
c = h[1] >>> 13;
|
|
194
|
+
h[1] &= 0x1fff;
|
|
195
|
+
h[2] += c;
|
|
196
|
+
|
|
197
|
+
g[0] = h[0] + 5;
|
|
198
|
+
c = g[0] >>> 13;
|
|
199
|
+
g[0] &= 0x1fff;
|
|
200
|
+
for (let i = 1; i < 10; i++) {
|
|
201
|
+
g[i] = h[i] + c;
|
|
202
|
+
c = g[i] >>> 13;
|
|
203
|
+
g[i] &= 0x1fff;
|
|
204
|
+
}
|
|
205
|
+
g[9] -= 1 << 13;
|
|
206
|
+
|
|
207
|
+
let mask = (c ^ 1) - 1;
|
|
208
|
+
|
|
209
|
+
for (let i = 0; i < 10; i++) g[i] &= mask;
|
|
210
|
+
mask = ~mask;
|
|
211
|
+
for (let i = 0; i < 10; i++) h[i] = (h[i] & mask) | g[i];
|
|
212
|
+
h[0] = (h[0] | (h[1] << 13)) & 0xffff;
|
|
213
|
+
h[1] = ((h[1] >>> 3) | (h[2] << 10)) & 0xffff;
|
|
214
|
+
h[2] = ((h[2] >>> 6) | (h[3] << 7)) & 0xffff;
|
|
215
|
+
h[3] = ((h[3] >>> 9) | (h[4] << 4)) & 0xffff;
|
|
216
|
+
h[4] = ((h[4] >>> 12) | (h[5] << 1) | (h[6] << 14)) & 0xffff;
|
|
217
|
+
h[5] = ((h[6] >>> 2) | (h[7] << 11)) & 0xffff;
|
|
218
|
+
h[6] = ((h[7] >>> 5) | (h[8] << 8)) & 0xffff;
|
|
219
|
+
h[7] = ((h[8] >>> 8) | (h[9] << 5)) & 0xffff;
|
|
220
|
+
|
|
221
|
+
let f = h[0] + pad[0];
|
|
222
|
+
|
|
223
|
+
h[0] = f & 0xffff;
|
|
224
|
+
for (let i = 1; i < 8; i++) {
|
|
225
|
+
f = (((h[i] + pad[i]) | 0) + (f >>> 16)) | 0;
|
|
226
|
+
h[i] = f & 0xffff;
|
|
227
|
+
}
|
|
228
|
+
g.fill(0);
|
|
229
|
+
}
|
|
230
|
+
update(data: Uint8Array): this {
|
|
231
|
+
const { buffer, blockLen } = this;
|
|
232
|
+
const len = data.length;
|
|
233
|
+
|
|
234
|
+
for (let pos = 0; pos < len;) {
|
|
235
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
236
|
+
|
|
237
|
+
// Fast path: we have at least one block in input
|
|
238
|
+
if (take === blockLen) {
|
|
239
|
+
for (; blockLen <= len - pos; pos += blockLen) this.process(data, pos);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
243
|
+
this.pos += take;
|
|
244
|
+
pos += take;
|
|
245
|
+
if (this.pos === blockLen) {
|
|
246
|
+
this.process(buffer, 0, false);
|
|
247
|
+
this.pos = 0;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return this;
|
|
252
|
+
}
|
|
253
|
+
destroy(): void {
|
|
254
|
+
this.h.fill(0);
|
|
255
|
+
this.r.fill(0);
|
|
256
|
+
this.buffer.fill(0);
|
|
257
|
+
this.pad.fill(0);
|
|
258
|
+
this.pos = 0;
|
|
259
|
+
}
|
|
260
|
+
digestInto(out: Uint8Array): Uint8Array {
|
|
261
|
+
const { buffer, h } = this;
|
|
262
|
+
let { pos } = this;
|
|
263
|
+
|
|
264
|
+
if (pos) {
|
|
265
|
+
buffer[pos++] = 1;
|
|
266
|
+
for (; pos < 16; pos++) buffer[pos] = 0;
|
|
267
|
+
this.process(buffer, 0, true);
|
|
268
|
+
}
|
|
269
|
+
this.finalize();
|
|
270
|
+
let opos = 0;
|
|
271
|
+
|
|
272
|
+
for (let i = 0; i < 8; i++) {
|
|
273
|
+
out[opos++] = h[i] >>> 0;
|
|
274
|
+
out[opos++] = h[i] >>> 8;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
279
|
+
digest(): Uint8Array {
|
|
280
|
+
const { buffer, outputLen } = this;
|
|
281
|
+
|
|
282
|
+
this.digestInto(buffer);
|
|
283
|
+
const res = buffer.slice(0, outputLen);
|
|
284
|
+
|
|
285
|
+
this.destroy();
|
|
286
|
+
|
|
287
|
+
return res;
|
|
288
|
+
}
|
|
289
|
+
}
|