@reallyme/codec 0.1.21 → 0.2.0

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/dist/pem.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { ReallyMeCodecProtoResult } from "./readOutput.js";
2
1
  export type ReallyMePemLabel = "PRIVATE KEY" | "EC PRIVATE KEY" | "PUBLIC KEY";
3
2
  export type ReallyMePemLineEnding = "lf" | "crlf";
4
3
  export type ReallyMePemDecodePolicy = Readonly<{
@@ -15,7 +14,5 @@ export type ReallyMePemDocument = Readonly<{
15
14
  label: ReallyMePemLabel;
16
15
  der: Uint8Array;
17
16
  }>;
18
- export declare const decodePem: (input: string, policy?: ReallyMePemDecodePolicy) => ReallyMePemDocument;
19
- export declare const decodePemProto: (input: string, policy?: ReallyMePemDecodePolicy) => Uint8Array;
20
- export declare const decodePemProtoResult: (input: string, policy?: ReallyMePemDecodePolicy) => ReallyMeCodecProtoResult;
21
- export declare const encodePem: (label: ReallyMePemLabel, der: Uint8Array, options?: ReallyMePemEncodeOptions) => string;
17
+ export declare const decodePem: (input: Uint8Array, policy?: ReallyMePemDecodePolicy) => ReallyMePemDocument;
18
+ export declare const encodePem: (label: ReallyMePemLabel, der: Uint8Array, options?: ReallyMePemEncodeOptions) => Uint8Array;
package/dist/pem.js CHANGED
@@ -1,14 +1,27 @@
1
1
  // SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
+ import { create } from "@bufbuild/protobuf";
4
5
  import { ReallyMeCodecError } from "./errors.js";
5
- import { ensureBytesInput, ensureStringInput, readBytesOutput, readBytesProperty, readObjectOutput, readProtoResultOutput, readStringOutput, readStringProperty, } from "./readOutput.js";
6
- import { requireReallyMeCodecWasmProvider } from "./wasmProvider.js";
6
+ import { CodecOperationRequestSchema, CodecPemDecodeOptionsSchema, CodecPemDecodeRequestSchema, CodecPemEncodeOptionsSchema, CodecPemEncodeRequestSchema, CodecPemLabel, CodecPemLineEnding, } from "./proto/generated/reallyme/codec/v1/codec_pb.js";
7
+ import { clearGeneratedOperationResult, processGeneratedOperationRequest, } from "./operationContract.js";
8
+ import { ensureBytesInput, snapshotBoundedBytesInput, } from "./readOutput.js";
7
9
  const validLabels = new Set([
8
10
  "PRIVATE KEY",
9
11
  "EC PRIVATE KEY",
10
12
  "PUBLIC KEY",
11
13
  ]);
14
+ const MAX_PEM_ALLOWED_LABELS = 1_024;
15
+ const pemDecodePolicyFields = new Set([
16
+ "allowedLabels",
17
+ "maxInputLen",
18
+ "maxDerLen",
19
+ ]);
20
+ const pemEncodeOptionFields = new Set([
21
+ "maxDerLen",
22
+ "lineWidth",
23
+ "lineEnding",
24
+ ]);
12
25
  const requirePemLabel = (label) => {
13
26
  if (!validLabels.has(label)) {
14
27
  throw new ReallyMeCodecError("invalid-input");
@@ -22,67 +35,278 @@ const requirePemLabel = (label) => {
22
35
  throw new ReallyMeCodecError("invalid-input");
23
36
  }
24
37
  };
38
+ const readPemLabel = (label) => {
39
+ if (!validLabels.has(label)) {
40
+ throw new ReallyMeCodecError("provider-failure");
41
+ }
42
+ switch (label) {
43
+ case "PRIVATE KEY":
44
+ case "EC PRIVATE KEY":
45
+ case "PUBLIC KEY":
46
+ return label;
47
+ default:
48
+ throw new ReallyMeCodecError("provider-failure");
49
+ }
50
+ };
51
+ const requireNoProviderUnknownFields = (message) => {
52
+ if (message.$unknown !== undefined && message.$unknown.length !== 0) {
53
+ throw new ReallyMeCodecError("provider-failure");
54
+ }
55
+ };
25
56
  const requirePositiveInteger = (value) => {
26
57
  if (value !== undefined &&
27
58
  (!Number.isSafeInteger(value) || value <= 0)) {
28
59
  throw new ReallyMeCodecError("invalid-input");
29
60
  }
30
61
  };
31
- const encodeOptions = (options) => {
32
- if (options === undefined) {
33
- return "";
62
+ const readSnapshotProperty = (value, name) => {
63
+ const descriptor = Object.getOwnPropertyDescriptor(value, name);
64
+ if (descriptor === undefined) {
65
+ return undefined;
66
+ }
67
+ if (!("value" in descriptor)) {
68
+ throw new ReallyMeCodecError("invalid-input");
69
+ }
70
+ return descriptor.value;
71
+ };
72
+ const snapshotOptionsRecord = (value, allowedFields) => {
73
+ try {
74
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
75
+ throw new ReallyMeCodecError("invalid-input");
76
+ }
77
+ const prototype = Object.getPrototypeOf(value);
78
+ if (prototype !== Object.prototype && prototype !== null) {
79
+ throw new ReallyMeCodecError("invalid-input");
80
+ }
81
+ const entries = [];
82
+ for (const key of Reflect.ownKeys(value)) {
83
+ if (typeof key !== "string" || !allowedFields.has(key)) {
84
+ throw new ReallyMeCodecError("invalid-input");
85
+ }
86
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
87
+ if (descriptor === undefined || !("value" in descriptor)) {
88
+ throw new ReallyMeCodecError("invalid-input");
89
+ }
90
+ entries.push([key, descriptor.value]);
91
+ }
92
+ return Object.fromEntries(entries);
93
+ }
94
+ catch (error) {
95
+ if (error instanceof ReallyMeCodecError) {
96
+ throw error;
97
+ }
98
+ throw new ReallyMeCodecError("invalid-input");
99
+ }
100
+ };
101
+ const readPositiveIntegerOption = (value, name) => {
102
+ const property = readSnapshotProperty(value, name);
103
+ if (property === undefined) {
104
+ return undefined;
105
+ }
106
+ if (typeof property !== "number") {
107
+ throw new ReallyMeCodecError("invalid-input");
108
+ }
109
+ requirePositiveInteger(property);
110
+ return property;
111
+ };
112
+ const snapshotDecodePolicy = (policy) => {
113
+ if (policy === undefined) {
114
+ return undefined;
115
+ }
116
+ const snapshot = snapshotOptionsRecord(policy, pemDecodePolicyFields);
117
+ const labelsValue = readSnapshotProperty(snapshot, "allowedLabels");
118
+ let allowedLabels;
119
+ if (labelsValue !== undefined) {
120
+ if (!Array.isArray(labelsValue) ||
121
+ Object.getPrototypeOf(labelsValue) !== Array.prototype ||
122
+ labelsValue.length > MAX_PEM_ALLOWED_LABELS) {
123
+ throw new ReallyMeCodecError("invalid-input");
124
+ }
125
+ allowedLabels = [];
126
+ for (let index = 0; index < labelsValue.length; index += 1) {
127
+ const label = readSnapshotProperty(labelsValue, String(index));
128
+ if (typeof label !== "string") {
129
+ throw new ReallyMeCodecError("invalid-input");
130
+ }
131
+ allowedLabels.push(requirePemLabel(label));
132
+ }
34
133
  }
35
- return JSON.stringify(options);
134
+ const maxInputLen = readPositiveIntegerOption(snapshot, "maxInputLen");
135
+ const maxDerLen = readPositiveIntegerOption(snapshot, "maxDerLen");
136
+ return {
137
+ ...(allowedLabels === undefined ? {} : { allowedLabels }),
138
+ ...(maxInputLen === undefined ? {} : { maxInputLen }),
139
+ ...(maxDerLen === undefined ? {} : { maxDerLen }),
140
+ };
36
141
  };
37
- const readPemDocument = (value) => {
38
- const object = readObjectOutput(value);
142
+ const snapshotEncodeOptions = (options) => {
143
+ if (options === undefined) {
144
+ return undefined;
145
+ }
146
+ const snapshot = snapshotOptionsRecord(options, pemEncodeOptionFields);
147
+ const maxDerLen = readPositiveIntegerOption(snapshot, "maxDerLen");
148
+ const lineWidth = readPositiveIntegerOption(snapshot, "lineWidth");
149
+ const lineEndingValue = readSnapshotProperty(snapshot, "lineEnding");
150
+ let lineEnding;
151
+ if (lineEndingValue !== undefined) {
152
+ if (lineEndingValue !== "lf" && lineEndingValue !== "crlf") {
153
+ throw new ReallyMeCodecError("invalid-input");
154
+ }
155
+ lineEnding = lineEndingValue;
156
+ }
39
157
  return {
40
- label: requirePemLabel(readStringProperty(object, "label")),
41
- der: readBytesProperty(object, "der"),
158
+ ...(maxDerLen === undefined ? {} : { maxDerLen }),
159
+ ...(lineWidth === undefined ? {} : { lineWidth }),
160
+ ...(lineEnding === undefined ? {} : { lineEnding }),
42
161
  };
43
162
  };
44
- export const decodePem = (input, policy) => {
45
- ensureStringInput(input);
46
- if (policy?.allowedLabels !== undefined) {
47
- for (const label of policy.allowedLabels) {
48
- requirePemLabel(label);
163
+ const requireProtoUint32 = (value) => {
164
+ requirePositiveInteger(value);
165
+ if (value === undefined) {
166
+ return 0;
167
+ }
168
+ if (value > 0xffff_ffff) {
169
+ throw new ReallyMeCodecError("invalid-input");
170
+ }
171
+ return value;
172
+ };
173
+ const protoPemLabel = (label) => {
174
+ switch (label) {
175
+ case "PRIVATE KEY":
176
+ return CodecPemLabel.PRIVATE_KEY;
177
+ case "EC PRIVATE KEY":
178
+ return CodecPemLabel.EC_PRIVATE_KEY;
179
+ case "PUBLIC KEY":
180
+ return CodecPemLabel.PUBLIC_KEY;
181
+ }
182
+ };
183
+ const readPemDocument = (result) => {
184
+ try {
185
+ requireNoProviderUnknownFields(result);
186
+ return {
187
+ label: readPemLabel(result.label),
188
+ der: result.der.slice(),
189
+ };
190
+ }
191
+ catch (error) {
192
+ if (error instanceof ReallyMeCodecError) {
193
+ throw error;
49
194
  }
195
+ throw new ReallyMeCodecError("provider-failure");
196
+ }
197
+ finally {
198
+ result.der.fill(0);
50
199
  }
51
- requirePositiveInteger(policy?.maxInputLen);
52
- requirePositiveInteger(policy?.maxDerLen);
53
- return readPemDocument(requireReallyMeCodecWasmProvider().pemDecode(input, encodeOptions(policy)));
54
200
  };
55
- export const decodePemProto = (input, policy) => {
56
- ensureStringInput(input);
57
- if (policy?.allowedLabels !== undefined) {
58
- for (const label of policy.allowedLabels) {
59
- requirePemLabel(label);
201
+ export const decodePem = (input, policy) => {
202
+ ensureBytesInput(input);
203
+ const request = pemDecodeRequest(input, policy);
204
+ try {
205
+ const operationResult = processGeneratedOperationRequest(request);
206
+ if (operationResult.result.case !== "pemDecode") {
207
+ clearGeneratedOperationResult(operationResult);
208
+ throw new ReallyMeCodecError("provider-failure");
60
209
  }
210
+ return readPemDocument(operationResult.result.value);
211
+ }
212
+ finally {
213
+ clearPemDecodeRequest(request);
61
214
  }
62
- requirePositiveInteger(policy?.maxInputLen);
63
- requirePositiveInteger(policy?.maxDerLen);
64
- return readBytesOutput(requireReallyMeCodecWasmProvider().pemDecodeProto(input, encodeOptions(policy)));
65
215
  };
66
- export const decodePemProtoResult = (input, policy) => {
67
- ensureStringInput(input);
68
- if (policy?.allowedLabels !== undefined) {
69
- for (const label of policy.allowedLabels) {
70
- requirePemLabel(label);
216
+ const pemDecodeRequest = (input, policy) => {
217
+ ensureBytesInput(input);
218
+ const snapshot = snapshotDecodePolicy(policy);
219
+ const allowedLabels = [];
220
+ if (snapshot?.allowedLabels !== undefined) {
221
+ for (const label of snapshot.allowedLabels) {
222
+ allowedLabels.push(protoPemLabel(requirePemLabel(label)));
71
223
  }
72
224
  }
73
- requirePositiveInteger(policy?.maxInputLen);
74
- requirePositiveInteger(policy?.maxDerLen);
75
- return readProtoResultOutput(requireReallyMeCodecWasmProvider().pemDecodeProtoResult(input, encodeOptions(policy)));
225
+ const options = snapshot === undefined
226
+ ? undefined
227
+ : create(CodecPemDecodeOptionsSchema, {
228
+ allowedLabels,
229
+ maxInputLen: requireProtoUint32(snapshot.maxInputLen),
230
+ maxDerLen: requireProtoUint32(snapshot.maxDerLen),
231
+ });
232
+ const pemSnapshot = snapshotBoundedBytesInput(input);
233
+ try {
234
+ const request = create(CodecPemDecodeRequestSchema, { pem: pemSnapshot });
235
+ if (options !== undefined) {
236
+ request.options = options;
237
+ }
238
+ return create(CodecOperationRequestSchema, {
239
+ operation: {
240
+ case: "pemDecode",
241
+ value: request,
242
+ },
243
+ });
244
+ }
245
+ catch (error) {
246
+ pemSnapshot.fill(0);
247
+ throw error;
248
+ }
249
+ };
250
+ const clearPemDecodeRequest = (request) => {
251
+ if (request.operation.case === "pemDecode") {
252
+ request.operation.value.pem.fill(0);
253
+ request.operation.value.pem = new Uint8Array(0);
254
+ }
76
255
  };
77
256
  export const encodePem = (label, der, options) => {
78
257
  requirePemLabel(label);
79
258
  ensureBytesInput(der);
80
- requirePositiveInteger(options?.maxDerLen);
81
- requirePositiveInteger(options?.lineWidth);
82
- if (options?.lineEnding !== undefined &&
83
- options.lineEnding !== "lf" &&
84
- options.lineEnding !== "crlf") {
85
- throw new ReallyMeCodecError("invalid-input");
259
+ const snapshot = snapshotEncodeOptions(options);
260
+ const protoOptions = snapshot === undefined
261
+ ? undefined
262
+ : create(CodecPemEncodeOptionsSchema, {
263
+ maxDerLen: requireProtoUint32(snapshot.maxDerLen),
264
+ lineWidth: requireProtoUint32(snapshot.lineWidth),
265
+ lineEnding: snapshot.lineEnding === "crlf"
266
+ ? CodecPemLineEnding.CRLF
267
+ : snapshot.lineEnding === "lf"
268
+ ? CodecPemLineEnding.LF
269
+ : CodecPemLineEnding.UNSPECIFIED,
270
+ });
271
+ const derSnapshot = snapshotBoundedBytesInput(der);
272
+ let request;
273
+ let operationRequest;
274
+ try {
275
+ request = create(CodecPemEncodeRequestSchema, {
276
+ label: protoPemLabel(label),
277
+ der: derSnapshot,
278
+ });
279
+ if (protoOptions !== undefined) {
280
+ request.options = protoOptions;
281
+ }
282
+ operationRequest = create(CodecOperationRequestSchema, {
283
+ operation: {
284
+ case: "pemEncode",
285
+ value: request,
286
+ },
287
+ });
288
+ }
289
+ catch (error) {
290
+ derSnapshot.fill(0);
291
+ throw error;
292
+ }
293
+ try {
294
+ const operationResult = processGeneratedOperationRequest(operationRequest);
295
+ if (operationResult.result.case !== "pemEncode") {
296
+ clearGeneratedOperationResult(operationResult);
297
+ throw new ReallyMeCodecError("provider-failure");
298
+ }
299
+ const result = operationResult.result.value;
300
+ try {
301
+ requireNoProviderUnknownFields(result);
302
+ return result.pem.slice();
303
+ }
304
+ finally {
305
+ result.pem.fill(0);
306
+ }
307
+ }
308
+ finally {
309
+ request.der.fill(0);
310
+ request.der = new Uint8Array(0);
86
311
  }
87
- return readStringOutput(requireReallyMeCodecWasmProvider().pemEncode(label, der, encodeOptions(options)));
88
312
  };