@yielded/crypto 0.1.0-beta.27 → 0.1.0-beta.29
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/README.md +4 -4
- package/dist/Aead.d.mts +26 -1
- package/dist/Aead.mjs +16 -1
- package/dist/Aead.mjs.map +1 -1
- package/dist/Signature.d.mts +23 -1
- package/dist/Signature.mjs +11 -5
- package/dist/Signature.mjs.map +1 -1
- package/dist/internal/aead.mjs +112 -68
- package/dist/internal/aead.mjs.map +1 -1
- package/dist/internal/argon2.mjs +8 -3
- package/dist/internal/argon2.mjs.map +1 -1
- package/dist/internal/hmac.mjs +12 -26
- package/dist/internal/hmac.mjs.map +1 -1
- package/dist/internal/scoped-key.mjs +31 -0
- package/dist/internal/scoped-key.mjs.map +1 -0
- package/dist/internal/signature.mjs +32 -1
- package/dist/internal/signature.mjs.map +1 -1
- package/dist/internal/xchacha-core.mjs +4 -2
- package/dist/internal/xchacha-core.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Aead.ts +35 -1
- package/src/Signature.ts +37 -4
- package/src/internal/aead.ts +97 -31
- package/src/internal/argon2.ts +13 -4
- package/src/internal/hmac.ts +15 -56
- package/src/internal/scoped-key.ts +60 -0
- package/src/internal/signature.ts +54 -0
- package/src/internal/xchacha-core.ts +6 -2
package/src/internal/hmac.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Effect,
|
|
1
|
+
import { Effect, Redacted, Schema } from "effect";
|
|
2
2
|
|
|
3
|
-
import { CryptoUnavailable } from "../Errors";
|
|
4
3
|
import { Hmac, Input, type Key, KeyInput, VerifyInput } from "../Hmac";
|
|
5
4
|
import { copy, decode, importError, nativeError, withSecret } from "./common";
|
|
5
|
+
import { makeScopedKey } from "./scoped-key";
|
|
6
6
|
|
|
7
7
|
const keyBytes = Schema.Uint8Array.check(Schema.isMinLength(1));
|
|
8
8
|
|
|
@@ -12,37 +12,7 @@ export const makeHmac = (subtle: SubtleCrypto): Hmac["Service"] => {
|
|
|
12
12
|
|
|
13
13
|
yield* decode(keyBytes, Redacted.value(value.key), "key");
|
|
14
14
|
|
|
15
|
-
|
|
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(
|
|
15
|
+
const use = yield* makeScopedKey(
|
|
46
16
|
withSecret(value.key, (material) =>
|
|
47
17
|
Effect.tryPromise({
|
|
48
18
|
try: () =>
|
|
@@ -51,41 +21,30 @@ export const makeHmac = (subtle: SubtleCrypto): Hmac["Service"] => {
|
|
|
51
21
|
"verify",
|
|
52
22
|
]),
|
|
53
23
|
catch: importError,
|
|
54
|
-
})
|
|
55
|
-
Effect.tap((key) =>
|
|
56
|
-
Effect.sync(() => {
|
|
57
|
-
imported = key;
|
|
58
|
-
}),
|
|
59
|
-
),
|
|
60
|
-
Effect.uninterruptible,
|
|
61
|
-
),
|
|
24
|
+
}),
|
|
62
25
|
),
|
|
63
|
-
).pipe(
|
|
64
|
-
Effect.onExit((exit) => (Exit.isFailure(exit) ? Scope.close(scope, exit) : Effect.void)),
|
|
65
26
|
);
|
|
66
27
|
|
|
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
28
|
return {
|
|
79
29
|
sign: Effect.fnUntraced(function* (input) {
|
|
80
30
|
const data = yield* decode(Schema.Uint8Array, input, "data").pipe(Effect.flatMap(copy));
|
|
81
31
|
|
|
82
|
-
return new Uint8Array(
|
|
32
|
+
return new Uint8Array(
|
|
33
|
+
yield* use((key) =>
|
|
34
|
+
Effect.tryPromise({ try: () => subtle.sign("HMAC", key, data), catch: nativeError }),
|
|
35
|
+
),
|
|
36
|
+
);
|
|
83
37
|
}),
|
|
84
38
|
verify: Effect.fnUntraced(function* (input, signature) {
|
|
85
39
|
const data = yield* decode(Schema.Uint8Array, input, "data").pipe(Effect.flatMap(copy));
|
|
86
40
|
const tag = yield* decode(Schema.Uint8Array, signature, "data").pipe(Effect.flatMap(copy));
|
|
87
41
|
|
|
88
|
-
return yield* use((key) =>
|
|
42
|
+
return yield* use((key) =>
|
|
43
|
+
Effect.tryPromise({
|
|
44
|
+
try: () => subtle.verify("HMAC", key, tag, data),
|
|
45
|
+
catch: nativeError,
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
89
48
|
}),
|
|
90
49
|
} satisfies Key;
|
|
91
50
|
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Effect, Exit, Fiber, Scope } from "effect";
|
|
2
|
+
|
|
3
|
+
import { CryptoUnavailable, type OperationError } from "../Errors";
|
|
4
|
+
|
|
5
|
+
/** Join native calls before releasing a key, including parallel parent closure. */
|
|
6
|
+
export const makeScopedKey = Effect.fnUntraced(function* (
|
|
7
|
+
acquire: Effect.Effect<CryptoKey, OperationError>,
|
|
8
|
+
) {
|
|
9
|
+
const scope = yield* Scope.fork(yield* Effect.scope, "sequential");
|
|
10
|
+
let imported: CryptoKey | undefined;
|
|
11
|
+
|
|
12
|
+
yield* Scope.addFinalizer(
|
|
13
|
+
scope,
|
|
14
|
+
Effect.sync(() => {
|
|
15
|
+
imported = undefined;
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const available = Effect.suspend(() =>
|
|
20
|
+
scope.state._tag === "Closed" ? Effect.fail(CryptoUnavailable.make({})) : Effect.void,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const run = <A, E>(operation: Effect.Effect<A, E>) =>
|
|
24
|
+
Effect.acquireUseRelease(
|
|
25
|
+
available.pipe(
|
|
26
|
+
Effect.andThen(
|
|
27
|
+
Effect.forkIn(operation, scope, { uninterruptible: false, startImmediately: true }),
|
|
28
|
+
),
|
|
29
|
+
),
|
|
30
|
+
(fiber) =>
|
|
31
|
+
Effect.gen(function* () {
|
|
32
|
+
const exit = yield* Fiber.await(fiber);
|
|
33
|
+
|
|
34
|
+
yield* available;
|
|
35
|
+
|
|
36
|
+
return yield* exit;
|
|
37
|
+
}),
|
|
38
|
+
Fiber.interrupt,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
yield* run(
|
|
42
|
+
acquire.pipe(
|
|
43
|
+
Effect.tap((key) =>
|
|
44
|
+
Effect.sync(() => {
|
|
45
|
+
imported = key;
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
48
|
+
Effect.uninterruptible,
|
|
49
|
+
),
|
|
50
|
+
).pipe(Effect.onExit((exit) => (Exit.isFailure(exit) ? Scope.close(scope, exit) : Effect.void)));
|
|
51
|
+
|
|
52
|
+
return <A, E>(operation: (key: CryptoKey) => Effect.Effect<A, E>) =>
|
|
53
|
+
run(
|
|
54
|
+
Effect.suspend((): Effect.Effect<A, E | CryptoUnavailable> => {
|
|
55
|
+
const key = imported;
|
|
56
|
+
|
|
57
|
+
return key === undefined ? Effect.fail(CryptoUnavailable.make({})) : operation(key);
|
|
58
|
+
}).pipe(Effect.uninterruptible),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
@@ -4,13 +4,18 @@ import { Base64Url } from "effect/encoding";
|
|
|
4
4
|
import { InvalidInput } from "../Errors";
|
|
5
5
|
import {
|
|
6
6
|
type Algorithm,
|
|
7
|
+
type PrivateKey,
|
|
8
|
+
PrivateKeyInput,
|
|
7
9
|
PrivateKeyParameters,
|
|
10
|
+
type PublicKey,
|
|
11
|
+
PublicKeyInput,
|
|
8
12
|
PublicKeyParameters,
|
|
9
13
|
SignInput,
|
|
10
14
|
Signature,
|
|
11
15
|
VerifyInput,
|
|
12
16
|
} from "../Signature";
|
|
13
17
|
import { copy, decode, importError, nativeError, withSecret } from "./common";
|
|
18
|
+
import { makeScopedKey } from "./scoped-key";
|
|
14
19
|
|
|
15
20
|
const derBytes = Schema.Uint8Array.check(Schema.isMinLength(1), Schema.isMaxLength(16384));
|
|
16
21
|
|
|
@@ -121,6 +126,55 @@ export const makeSignature = (subtle: SubtleCrypto): Signature["Service"] => {
|
|
|
121
126
|
});
|
|
122
127
|
|
|
123
128
|
return Signature.of({
|
|
129
|
+
importPrivateKey: Effect.fnUntraced(function* (input) {
|
|
130
|
+
const value = yield* decode(PrivateKeyInput, input, "key");
|
|
131
|
+
const algorithm = value.algorithm;
|
|
132
|
+
|
|
133
|
+
const use = yield* makeScopedKey(
|
|
134
|
+
withSecret(value.privateKey, (material) => importKey(algorithm, "pkcs8", material)),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
sign: Effect.fnUntraced(function* (input) {
|
|
139
|
+
const data = yield* decode(Schema.Uint8Array, input, "data").pipe(Effect.flatMap(copy));
|
|
140
|
+
|
|
141
|
+
const result = yield* use((key) =>
|
|
142
|
+
Effect.tryPromise({
|
|
143
|
+
try: () => subtle.sign(operationAlgorithm(algorithm), key, data),
|
|
144
|
+
catch: nativeError,
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
return new Uint8Array(result);
|
|
149
|
+
}),
|
|
150
|
+
} satisfies PrivateKey;
|
|
151
|
+
}),
|
|
152
|
+
importPublicKey: Effect.fnUntraced(function* (input) {
|
|
153
|
+
const value = yield* decode(PublicKeyInput, input, "key");
|
|
154
|
+
const algorithm = value.algorithm;
|
|
155
|
+
const material = yield* copy(value.publicKey);
|
|
156
|
+
const use = yield* makeScopedKey(importKey(algorithm, "spki", material));
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
verify: Effect.fnUntraced(function* (input, signature) {
|
|
160
|
+
const data = yield* decode(Schema.Uint8Array, input, "data").pipe(Effect.flatMap(copy));
|
|
161
|
+
|
|
162
|
+
const tag = yield* decode(Schema.Uint8Array, signature, "data").pipe(
|
|
163
|
+
Effect.flatMap(copy),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
return yield* use((key) => {
|
|
167
|
+
if ((algorithm === "ECDSA-P256-SHA256" || algorithm === "Ed25519") && tag.length !== 64)
|
|
168
|
+
return Effect.succeed(false);
|
|
169
|
+
|
|
170
|
+
return Effect.tryPromise({
|
|
171
|
+
try: () => subtle.verify(operationAlgorithm(algorithm), key, tag, data),
|
|
172
|
+
catch: nativeError,
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}),
|
|
176
|
+
} satisfies PublicKey;
|
|
177
|
+
}),
|
|
124
178
|
encodePublicKey: Effect.fnUntraced(function* (input) {
|
|
125
179
|
const parameters = yield* decode(PublicKeyParameters, input, "key");
|
|
126
180
|
const key = yield* importComponents(parameters, "verify");
|
|
@@ -258,6 +258,7 @@ interface State {
|
|
|
258
258
|
readonly nonce: Uint32Array;
|
|
259
259
|
readonly words: Uint32Array;
|
|
260
260
|
readonly stream: Uint8Array;
|
|
261
|
+
readonly streamView: DataView;
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
const readWords = (data: Uint8Array, words: Uint32Array): void => {
|
|
@@ -270,11 +271,14 @@ function withState<A>(key: Uint8Array, nonce: Uint8Array, operation: (state: Sta
|
|
|
270
271
|
const original = new Uint32Array(8);
|
|
271
272
|
const extendedNonce = new Uint32Array(4);
|
|
272
273
|
|
|
274
|
+
const stream = new Uint8Array(64);
|
|
275
|
+
|
|
273
276
|
const state: State = {
|
|
274
277
|
key: new Uint32Array(8),
|
|
275
278
|
nonce: new Uint32Array(3),
|
|
276
279
|
words: new Uint32Array(16),
|
|
277
|
-
stream
|
|
280
|
+
stream,
|
|
281
|
+
streamView: new DataView(stream.buffer),
|
|
278
282
|
};
|
|
279
283
|
|
|
280
284
|
try {
|
|
@@ -300,7 +304,7 @@ function withState<A>(key: Uint8Array, nonce: Uint8Array, operation: (state: Sta
|
|
|
300
304
|
|
|
301
305
|
const blockAt = (state: State, counter: number): void => {
|
|
302
306
|
chachaCore(sigma, state.key, state.nonce, state.words, counter);
|
|
303
|
-
const view =
|
|
307
|
+
const view = state.streamView;
|
|
304
308
|
|
|
305
309
|
for (let i = 0; i < 16; i++) view.setUint32(i * 4, state.words[i], true);
|
|
306
310
|
};
|