edockit 0.3.0 → 0.4.0-dev.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/CHANGELOG.md +17 -0
- package/README.md +81 -198
- package/TRUSTED-LIST.md +308 -0
- package/dist/certificate-c46e14a0.js +560 -0
- package/dist/certificate-c46e14a0.js.map +1 -0
- package/dist/certificate-fc0e06f7.js +571 -0
- package/dist/certificate-fc0e06f7.js.map +1 -0
- package/dist/core/canonicalization/XMLCanonicalizer.d.ts +9 -3
- package/dist/core/trustedlist/build.d.ts +41 -0
- package/dist/core/trustedlist/bundled-provider.d.ts +2 -0
- package/dist/core/trustedlist/contract.d.ts +19 -0
- package/dist/core/trustedlist/dom.d.ts +12 -0
- package/dist/core/trustedlist/extract.d.ts +6 -0
- package/dist/core/trustedlist/http.d.ts +8 -0
- package/dist/core/trustedlist/identity.d.ts +7 -0
- package/dist/core/trustedlist/index.d.ts +18 -0
- package/dist/core/trustedlist/loader.d.ts +5 -0
- package/dist/core/trustedlist/matcher.d.ts +11 -0
- package/dist/core/trustedlist/normalize.d.ts +14 -0
- package/dist/core/trustedlist/reference-provider.d.ts +12 -0
- package/dist/core/trustedlist/types.d.ts +114 -0
- package/dist/core/unzip.d.ts +0 -0
- package/dist/core/verification.d.ts +22 -0
- package/dist/data/trusted-list.d.ts +3 -0
- package/dist/identity-1a3dddc3.js +902 -0
- package/dist/identity-1a3dddc3.js.map +1 -0
- package/dist/identity-b3a70fc1.js +897 -0
- package/dist/identity-b3a70fc1.js.map +1 -0
- package/dist/index.cjs.js +909 -8003
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.esm.js +417 -7510
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +12 -12
- package/dist/index.umd.js.map +1 -1
- package/dist/loader-1ac52e12.js +217 -0
- package/dist/loader-1ac52e12.js.map +1 -0
- package/dist/loader-43d8e17a.js +222 -0
- package/dist/loader-43d8e17a.js.map +1 -0
- package/dist/normalize-60f2d7e6.js +6270 -0
- package/dist/normalize-60f2d7e6.js.map +1 -0
- package/dist/normalize-70da6516.js +6214 -0
- package/dist/normalize-70da6516.js.map +1 -0
- package/dist/reference-provider-1cd85b7b.js +217 -0
- package/dist/reference-provider-1cd85b7b.js.map +1 -0
- package/dist/reference-provider-53240217.js +211 -0
- package/dist/reference-provider-53240217.js.map +1 -0
- package/dist/trusted-list-build.cjs.js +575 -0
- package/dist/trusted-list-build.cjs.js.map +1 -0
- package/dist/trusted-list-build.d.ts +4 -0
- package/dist/trusted-list-build.esm.js +564 -0
- package/dist/trusted-list-build.esm.js.map +1 -0
- package/dist/trusted-list-bundled.cjs.js +30436 -0
- package/dist/trusted-list-bundled.cjs.js.map +1 -0
- package/dist/trusted-list-bundled.d.ts +1 -0
- package/dist/trusted-list-bundled.esm.js +30432 -0
- package/dist/trusted-list-bundled.esm.js.map +1 -0
- package/dist/trusted-list-http.cjs.js +85 -0
- package/dist/trusted-list-http.cjs.js.map +1 -0
- package/dist/trusted-list-http.d.ts +1 -0
- package/dist/trusted-list-http.esm.js +81 -0
- package/dist/trusted-list-http.esm.js.map +1 -0
- package/dist/trusted-list.cjs.js +35 -0
- package/dist/trusted-list.cjs.js.map +1 -0
- package/dist/trusted-list.d.ts +9 -0
- package/dist/trusted-list.esm.js +10 -0
- package/dist/trusted-list.esm.js.map +1 -0
- package/package.json +32 -2
|
@@ -0,0 +1,902 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* MIT License
|
|
3
|
+
* Copyright (c) 2025 Edgars Jēkabsons, ZenomyTech SIA
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
var x509 = require('@peculiar/x509');
|
|
8
|
+
var normalize = require('./normalize-60f2d7e6.js');
|
|
9
|
+
|
|
10
|
+
class AsnSchemaValidationError extends Error {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this.schemas = [];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class AsnParser {
|
|
18
|
+
static parse(data, target) {
|
|
19
|
+
const asn1Parsed = normalize.fromBER(data);
|
|
20
|
+
if (asn1Parsed.result.error) {
|
|
21
|
+
throw new Error(asn1Parsed.result.error);
|
|
22
|
+
}
|
|
23
|
+
const res = this.fromASN(asn1Parsed.result, target);
|
|
24
|
+
return res;
|
|
25
|
+
}
|
|
26
|
+
static fromASN(asn1Schema, target) {
|
|
27
|
+
try {
|
|
28
|
+
if (normalize.isConvertible(target)) {
|
|
29
|
+
const value = new target();
|
|
30
|
+
return value.fromASN(asn1Schema);
|
|
31
|
+
}
|
|
32
|
+
const schema = normalize.schemaStorage.get(target);
|
|
33
|
+
normalize.schemaStorage.cache(target);
|
|
34
|
+
let targetSchema = schema.schema;
|
|
35
|
+
const choiceResult = this.handleChoiceTypes(asn1Schema, schema, target, targetSchema);
|
|
36
|
+
if (choiceResult === null || choiceResult === void 0 ? void 0 : choiceResult.result) {
|
|
37
|
+
return choiceResult.result;
|
|
38
|
+
}
|
|
39
|
+
if (choiceResult === null || choiceResult === void 0 ? void 0 : choiceResult.targetSchema) {
|
|
40
|
+
targetSchema = choiceResult.targetSchema;
|
|
41
|
+
}
|
|
42
|
+
const sequenceResult = this.handleSequenceTypes(asn1Schema, schema, target, targetSchema);
|
|
43
|
+
const res = new target();
|
|
44
|
+
if (normalize.isTypeOfArray(target)) {
|
|
45
|
+
return this.handleArrayTypes(asn1Schema, schema, target);
|
|
46
|
+
}
|
|
47
|
+
this.processSchemaItems(schema, sequenceResult, res);
|
|
48
|
+
return res;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof AsnSchemaValidationError) {
|
|
52
|
+
error.schemas.push(target.name);
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
static handleChoiceTypes(asn1Schema, schema, target, targetSchema) {
|
|
58
|
+
if (asn1Schema.constructor === normalize.Constructed &&
|
|
59
|
+
schema.type === normalize.AsnTypeTypes.Choice &&
|
|
60
|
+
asn1Schema.idBlock.tagClass === 3) {
|
|
61
|
+
for (const key in schema.items) {
|
|
62
|
+
const schemaItem = schema.items[key];
|
|
63
|
+
if (schemaItem.context === asn1Schema.idBlock.tagNumber && schemaItem.implicit) {
|
|
64
|
+
if (typeof schemaItem.type === "function" &&
|
|
65
|
+
normalize.schemaStorage.has(schemaItem.type)) {
|
|
66
|
+
const fieldSchema = normalize.schemaStorage.get(schemaItem.type);
|
|
67
|
+
if (fieldSchema && fieldSchema.type === normalize.AsnTypeTypes.Sequence) {
|
|
68
|
+
const newSeq = new normalize.Sequence();
|
|
69
|
+
if ("value" in asn1Schema.valueBlock &&
|
|
70
|
+
Array.isArray(asn1Schema.valueBlock.value) &&
|
|
71
|
+
"value" in newSeq.valueBlock) {
|
|
72
|
+
newSeq.valueBlock.value = asn1Schema.valueBlock.value;
|
|
73
|
+
const fieldValue = this.fromASN(newSeq, schemaItem.type);
|
|
74
|
+
const res = new target();
|
|
75
|
+
res[key] = fieldValue;
|
|
76
|
+
return { result: res };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (asn1Schema.constructor === normalize.Constructed &&
|
|
84
|
+
schema.type !== normalize.AsnTypeTypes.Choice) {
|
|
85
|
+
const newTargetSchema = new normalize.Constructed({
|
|
86
|
+
idBlock: {
|
|
87
|
+
tagClass: 3,
|
|
88
|
+
tagNumber: asn1Schema.idBlock.tagNumber,
|
|
89
|
+
},
|
|
90
|
+
value: schema.schema.valueBlock.value,
|
|
91
|
+
});
|
|
92
|
+
for (const key in schema.items) {
|
|
93
|
+
delete asn1Schema[key];
|
|
94
|
+
}
|
|
95
|
+
return { targetSchema: newTargetSchema };
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
static handleSequenceTypes(asn1Schema, schema, target, targetSchema) {
|
|
100
|
+
if (schema.type === normalize.AsnTypeTypes.Sequence) {
|
|
101
|
+
const asn1ComparedSchema = normalize.compareSchema({}, asn1Schema, targetSchema);
|
|
102
|
+
if (!asn1ComparedSchema.verified) {
|
|
103
|
+
throw new AsnSchemaValidationError(`Data does not match to ${target.name} ASN1 schema.${asn1ComparedSchema.result.error ? ` ${asn1ComparedSchema.result.error}` : ""}`);
|
|
104
|
+
}
|
|
105
|
+
return asn1ComparedSchema;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const asn1ComparedSchema = normalize.compareSchema({}, asn1Schema, targetSchema);
|
|
109
|
+
if (!asn1ComparedSchema.verified) {
|
|
110
|
+
throw new AsnSchemaValidationError(`Data does not match to ${target.name} ASN1 schema.${asn1ComparedSchema.result.error ? ` ${asn1ComparedSchema.result.error}` : ""}`);
|
|
111
|
+
}
|
|
112
|
+
return asn1ComparedSchema;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
static processRepeatedField(asn1Elements, asn1Index, schemaItem) {
|
|
116
|
+
let elementsToProcess = asn1Elements.slice(asn1Index);
|
|
117
|
+
if (elementsToProcess.length === 1 && elementsToProcess[0].constructor.name === "Sequence") {
|
|
118
|
+
const seq = elementsToProcess[0];
|
|
119
|
+
if (seq.valueBlock && seq.valueBlock.value && Array.isArray(seq.valueBlock.value)) {
|
|
120
|
+
elementsToProcess = seq.valueBlock.value;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (typeof schemaItem.type === "number") {
|
|
124
|
+
const converter = normalize.defaultConverter(schemaItem.type);
|
|
125
|
+
if (!converter)
|
|
126
|
+
throw new Error(`No converter for ASN.1 type ${schemaItem.type}`);
|
|
127
|
+
return elementsToProcess
|
|
128
|
+
.filter((el) => el && el.valueBlock)
|
|
129
|
+
.map((el) => {
|
|
130
|
+
try {
|
|
131
|
+
return converter.fromASN(el);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
.filter((v) => v !== undefined);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
return elementsToProcess
|
|
141
|
+
.filter((el) => el && el.valueBlock)
|
|
142
|
+
.map((el) => {
|
|
143
|
+
try {
|
|
144
|
+
return this.fromASN(el, schemaItem.type);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
.filter((v) => v !== undefined);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
static processPrimitiveField(asn1Element, schemaItem) {
|
|
154
|
+
const converter = normalize.defaultConverter(schemaItem.type);
|
|
155
|
+
if (!converter)
|
|
156
|
+
throw new Error(`No converter for ASN.1 type ${schemaItem.type}`);
|
|
157
|
+
return converter.fromASN(asn1Element);
|
|
158
|
+
}
|
|
159
|
+
static isOptionalChoiceField(schemaItem) {
|
|
160
|
+
return (schemaItem.optional &&
|
|
161
|
+
typeof schemaItem.type === "function" &&
|
|
162
|
+
normalize.schemaStorage.has(schemaItem.type) &&
|
|
163
|
+
normalize.schemaStorage.get(schemaItem.type).type === normalize.AsnTypeTypes.Choice);
|
|
164
|
+
}
|
|
165
|
+
static processOptionalChoiceField(asn1Element, schemaItem) {
|
|
166
|
+
try {
|
|
167
|
+
const value = this.fromASN(asn1Element, schemaItem.type);
|
|
168
|
+
return { processed: true, value };
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
if (err instanceof AsnSchemaValidationError &&
|
|
172
|
+
/Wrong values for Choice type/.test(err.message)) {
|
|
173
|
+
return { processed: false };
|
|
174
|
+
}
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
static handleArrayTypes(asn1Schema, schema, target) {
|
|
179
|
+
if (!("value" in asn1Schema.valueBlock && Array.isArray(asn1Schema.valueBlock.value))) {
|
|
180
|
+
throw new Error(`Cannot get items from the ASN.1 parsed value. ASN.1 object is not constructed.`);
|
|
181
|
+
}
|
|
182
|
+
const itemType = schema.itemType;
|
|
183
|
+
if (typeof itemType === "number") {
|
|
184
|
+
const converter = normalize.defaultConverter(itemType);
|
|
185
|
+
if (!converter) {
|
|
186
|
+
throw new Error(`Cannot get default converter for array item of ${target.name} ASN1 schema`);
|
|
187
|
+
}
|
|
188
|
+
return target.from(asn1Schema.valueBlock.value, (element) => converter.fromASN(element));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
return target.from(asn1Schema.valueBlock.value, (element) => this.fromASN(element, itemType));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
static processSchemaItems(schema, asn1ComparedSchema, res) {
|
|
195
|
+
for (const key in schema.items) {
|
|
196
|
+
const asn1SchemaValue = asn1ComparedSchema.result[key];
|
|
197
|
+
if (!asn1SchemaValue) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const schemaItem = schema.items[key];
|
|
201
|
+
const schemaItemType = schemaItem.type;
|
|
202
|
+
let parsedValue;
|
|
203
|
+
if (typeof schemaItemType === "number" || normalize.isConvertible(schemaItemType)) {
|
|
204
|
+
parsedValue = this.processPrimitiveSchemaItem(asn1SchemaValue, schemaItem, schemaItemType);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
parsedValue = this.processComplexSchemaItem(asn1SchemaValue, schemaItem, schemaItemType);
|
|
208
|
+
}
|
|
209
|
+
if (parsedValue &&
|
|
210
|
+
typeof parsedValue === "object" &&
|
|
211
|
+
"value" in parsedValue &&
|
|
212
|
+
"raw" in parsedValue) {
|
|
213
|
+
res[key] = parsedValue.value;
|
|
214
|
+
res[`${key}Raw`] = parsedValue.raw;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
res[key] = parsedValue;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
static processPrimitiveSchemaItem(asn1SchemaValue, schemaItem, schemaItemType) {
|
|
222
|
+
var _a;
|
|
223
|
+
const converter = (_a = schemaItem.converter) !== null && _a !== void 0 ? _a : (normalize.isConvertible(schemaItemType)
|
|
224
|
+
? new schemaItemType()
|
|
225
|
+
: null);
|
|
226
|
+
if (!converter) {
|
|
227
|
+
throw new Error("Converter is empty");
|
|
228
|
+
}
|
|
229
|
+
if (schemaItem.repeated) {
|
|
230
|
+
return this.processRepeatedPrimitiveItem(asn1SchemaValue, schemaItem, converter);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
return this.processSinglePrimitiveItem(asn1SchemaValue, schemaItem, schemaItemType, converter);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
static processRepeatedPrimitiveItem(asn1SchemaValue, schemaItem, converter) {
|
|
237
|
+
if (schemaItem.implicit) {
|
|
238
|
+
const Container = schemaItem.repeated === "sequence" ? normalize.Sequence : normalize.Set;
|
|
239
|
+
const newItem = new Container();
|
|
240
|
+
newItem.valueBlock = asn1SchemaValue.valueBlock;
|
|
241
|
+
const newItemAsn = normalize.fromBER(newItem.toBER(false));
|
|
242
|
+
if (newItemAsn.offset === -1) {
|
|
243
|
+
throw new Error(`Cannot parse the child item. ${newItemAsn.result.error}`);
|
|
244
|
+
}
|
|
245
|
+
if (!("value" in newItemAsn.result.valueBlock &&
|
|
246
|
+
Array.isArray(newItemAsn.result.valueBlock.value))) {
|
|
247
|
+
throw new Error("Cannot get items from the ASN.1 parsed value. ASN.1 object is not constructed.");
|
|
248
|
+
}
|
|
249
|
+
const value = newItemAsn.result.valueBlock.value;
|
|
250
|
+
return Array.from(value, (element) => converter.fromASN(element));
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
return Array.from(asn1SchemaValue, (element) => converter.fromASN(element));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
static processSinglePrimitiveItem(asn1SchemaValue, schemaItem, schemaItemType, converter) {
|
|
257
|
+
let value = asn1SchemaValue;
|
|
258
|
+
if (schemaItem.implicit) {
|
|
259
|
+
let newItem;
|
|
260
|
+
if (normalize.isConvertible(schemaItemType)) {
|
|
261
|
+
newItem = new schemaItemType().toSchema("");
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
const Asn1TypeName = normalize.AsnPropTypes[schemaItemType];
|
|
265
|
+
const Asn1Type = normalize.asn1js[Asn1TypeName];
|
|
266
|
+
if (!Asn1Type) {
|
|
267
|
+
throw new Error(`Cannot get '${Asn1TypeName}' class from asn1js module`);
|
|
268
|
+
}
|
|
269
|
+
newItem = new Asn1Type();
|
|
270
|
+
}
|
|
271
|
+
newItem.valueBlock = value.valueBlock;
|
|
272
|
+
value = normalize.fromBER(newItem.toBER(false)).result;
|
|
273
|
+
}
|
|
274
|
+
return converter.fromASN(value);
|
|
275
|
+
}
|
|
276
|
+
static processComplexSchemaItem(asn1SchemaValue, schemaItem, schemaItemType) {
|
|
277
|
+
if (schemaItem.repeated) {
|
|
278
|
+
if (!Array.isArray(asn1SchemaValue)) {
|
|
279
|
+
throw new Error("Cannot get list of items from the ASN.1 parsed value. ASN.1 value should be iterable.");
|
|
280
|
+
}
|
|
281
|
+
return Array.from(asn1SchemaValue, (element) => this.fromASN(element, schemaItemType));
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
const valueToProcess = this.handleImplicitTagging(asn1SchemaValue, schemaItem, schemaItemType);
|
|
285
|
+
if (this.isOptionalChoiceField(schemaItem)) {
|
|
286
|
+
try {
|
|
287
|
+
return this.fromASN(valueToProcess, schemaItemType);
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
if (err instanceof AsnSchemaValidationError &&
|
|
291
|
+
/Wrong values for Choice type/.test(err.message)) {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
throw err;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const parsedValue = this.fromASN(valueToProcess, schemaItemType);
|
|
299
|
+
if (schemaItem.raw) {
|
|
300
|
+
return {
|
|
301
|
+
value: parsedValue,
|
|
302
|
+
raw: asn1SchemaValue.valueBeforeDecodeView,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return parsedValue;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
static handleImplicitTagging(asn1SchemaValue, schemaItem, schemaItemType) {
|
|
310
|
+
if (schemaItem.implicit && typeof schemaItem.context === "number") {
|
|
311
|
+
const schema = normalize.schemaStorage.get(schemaItemType);
|
|
312
|
+
if (schema.type === normalize.AsnTypeTypes.Sequence) {
|
|
313
|
+
const newSeq = new normalize.Sequence();
|
|
314
|
+
if ("value" in asn1SchemaValue.valueBlock &&
|
|
315
|
+
Array.isArray(asn1SchemaValue.valueBlock.value) &&
|
|
316
|
+
"value" in newSeq.valueBlock) {
|
|
317
|
+
newSeq.valueBlock.value = asn1SchemaValue.valueBlock.value;
|
|
318
|
+
return newSeq;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else if (schema.type === normalize.AsnTypeTypes.Set) {
|
|
322
|
+
const newSet = new normalize.Set();
|
|
323
|
+
if ("value" in asn1SchemaValue.valueBlock &&
|
|
324
|
+
Array.isArray(asn1SchemaValue.valueBlock.value) &&
|
|
325
|
+
"value" in newSet.valueBlock) {
|
|
326
|
+
newSet.valueBlock.value = asn1SchemaValue.valueBlock.value;
|
|
327
|
+
return newSet;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return asn1SchemaValue;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
class AsnSerializer {
|
|
336
|
+
static serialize(obj) {
|
|
337
|
+
if (obj instanceof normalize.BaseBlock) {
|
|
338
|
+
return obj.toBER(false);
|
|
339
|
+
}
|
|
340
|
+
return this.toASN(obj).toBER(false);
|
|
341
|
+
}
|
|
342
|
+
static toASN(obj) {
|
|
343
|
+
if (obj && typeof obj === "object" && normalize.isConvertible(obj)) {
|
|
344
|
+
return obj.toASN();
|
|
345
|
+
}
|
|
346
|
+
if (!(obj && typeof obj === "object")) {
|
|
347
|
+
throw new TypeError("Parameter 1 should be type of Object.");
|
|
348
|
+
}
|
|
349
|
+
const target = obj.constructor;
|
|
350
|
+
const schema = normalize.schemaStorage.get(target);
|
|
351
|
+
normalize.schemaStorage.cache(target);
|
|
352
|
+
let asn1Value = [];
|
|
353
|
+
if (schema.itemType) {
|
|
354
|
+
if (!Array.isArray(obj)) {
|
|
355
|
+
throw new TypeError("Parameter 1 should be type of Array.");
|
|
356
|
+
}
|
|
357
|
+
if (typeof schema.itemType === "number") {
|
|
358
|
+
const converter = normalize.defaultConverter(schema.itemType);
|
|
359
|
+
if (!converter) {
|
|
360
|
+
throw new Error(`Cannot get default converter for array item of ${target.name} ASN1 schema`);
|
|
361
|
+
}
|
|
362
|
+
asn1Value = obj.map((o) => converter.toASN(o));
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
asn1Value = obj.map((o) => this.toAsnItem({ type: schema.itemType }, "[]", target, o));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
for (const key in schema.items) {
|
|
370
|
+
const schemaItem = schema.items[key];
|
|
371
|
+
const objProp = obj[key];
|
|
372
|
+
if (objProp === undefined ||
|
|
373
|
+
schemaItem.defaultValue === objProp ||
|
|
374
|
+
(typeof schemaItem.defaultValue === "object" &&
|
|
375
|
+
typeof objProp === "object" &&
|
|
376
|
+
normalize.isArrayEqual(this.serialize(schemaItem.defaultValue), this.serialize(objProp)))) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
const asn1Item = AsnSerializer.toAsnItem(schemaItem, key, target, objProp);
|
|
380
|
+
if (typeof schemaItem.context === "number") {
|
|
381
|
+
if (schemaItem.implicit) {
|
|
382
|
+
if (!schemaItem.repeated &&
|
|
383
|
+
(typeof schemaItem.type === "number" || normalize.isConvertible(schemaItem.type))) {
|
|
384
|
+
const value = {};
|
|
385
|
+
value.valueHex =
|
|
386
|
+
asn1Item instanceof normalize.Null
|
|
387
|
+
? asn1Item.valueBeforeDecodeView
|
|
388
|
+
: asn1Item.valueBlock.toBER();
|
|
389
|
+
asn1Value.push(new normalize.Primitive({
|
|
390
|
+
optional: schemaItem.optional,
|
|
391
|
+
idBlock: {
|
|
392
|
+
tagClass: 3,
|
|
393
|
+
tagNumber: schemaItem.context,
|
|
394
|
+
},
|
|
395
|
+
...value,
|
|
396
|
+
}));
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
asn1Value.push(new normalize.Constructed({
|
|
400
|
+
optional: schemaItem.optional,
|
|
401
|
+
idBlock: {
|
|
402
|
+
tagClass: 3,
|
|
403
|
+
tagNumber: schemaItem.context,
|
|
404
|
+
},
|
|
405
|
+
value: asn1Item.valueBlock.value,
|
|
406
|
+
}));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
asn1Value.push(new normalize.Constructed({
|
|
411
|
+
optional: schemaItem.optional,
|
|
412
|
+
idBlock: {
|
|
413
|
+
tagClass: 3,
|
|
414
|
+
tagNumber: schemaItem.context,
|
|
415
|
+
},
|
|
416
|
+
value: [asn1Item],
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
else if (schemaItem.repeated) {
|
|
421
|
+
asn1Value = asn1Value.concat(asn1Item);
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
asn1Value.push(asn1Item);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
let asnSchema;
|
|
429
|
+
switch (schema.type) {
|
|
430
|
+
case normalize.AsnTypeTypes.Sequence:
|
|
431
|
+
asnSchema = new normalize.Sequence({ value: asn1Value });
|
|
432
|
+
break;
|
|
433
|
+
case normalize.AsnTypeTypes.Set:
|
|
434
|
+
asnSchema = new normalize.Set({ value: asn1Value });
|
|
435
|
+
break;
|
|
436
|
+
case normalize.AsnTypeTypes.Choice:
|
|
437
|
+
if (!asn1Value[0]) {
|
|
438
|
+
throw new Error(`Schema '${target.name}' has wrong data. Choice cannot be empty.`);
|
|
439
|
+
}
|
|
440
|
+
asnSchema = asn1Value[0];
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
return asnSchema;
|
|
444
|
+
}
|
|
445
|
+
static toAsnItem(schemaItem, key, target, objProp) {
|
|
446
|
+
let asn1Item;
|
|
447
|
+
if (typeof schemaItem.type === "number") {
|
|
448
|
+
const converter = schemaItem.converter;
|
|
449
|
+
if (!converter) {
|
|
450
|
+
throw new Error(`Property '${key}' doesn't have converter for type ${normalize.AsnPropTypes[schemaItem.type]} in schema '${target.name}'`);
|
|
451
|
+
}
|
|
452
|
+
if (schemaItem.repeated) {
|
|
453
|
+
if (!Array.isArray(objProp)) {
|
|
454
|
+
throw new TypeError("Parameter 'objProp' should be type of Array.");
|
|
455
|
+
}
|
|
456
|
+
const items = Array.from(objProp, (element) => converter.toASN(element));
|
|
457
|
+
const Container = schemaItem.repeated === "sequence" ? normalize.Sequence : normalize.Set;
|
|
458
|
+
asn1Item = new Container({
|
|
459
|
+
value: items,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
asn1Item = converter.toASN(objProp);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
if (schemaItem.repeated) {
|
|
468
|
+
if (!Array.isArray(objProp)) {
|
|
469
|
+
throw new TypeError("Parameter 'objProp' should be type of Array.");
|
|
470
|
+
}
|
|
471
|
+
const items = Array.from(objProp, (element) => this.toASN(element));
|
|
472
|
+
const Container = schemaItem.repeated === "sequence" ? normalize.Sequence : normalize.Set;
|
|
473
|
+
asn1Item = new Container({
|
|
474
|
+
value: items,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
asn1Item = this.toASN(objProp);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return asn1Item;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
class AsnConvert {
|
|
486
|
+
static serialize(obj) {
|
|
487
|
+
return AsnSerializer.serialize(obj);
|
|
488
|
+
}
|
|
489
|
+
static parse(data, target) {
|
|
490
|
+
return AsnParser.parse(data, target);
|
|
491
|
+
}
|
|
492
|
+
static toString(data) {
|
|
493
|
+
const buf = normalize.BufferSourceConverter.isBufferSource(data)
|
|
494
|
+
? normalize.BufferSourceConverter.toArrayBuffer(data)
|
|
495
|
+
: AsnConvert.serialize(data);
|
|
496
|
+
const asn = normalize.fromBER(buf);
|
|
497
|
+
if (asn.offset === -1) {
|
|
498
|
+
throw new Error(`Cannot decode ASN.1 data. ${asn.result.error}`);
|
|
499
|
+
}
|
|
500
|
+
return asn.result.toString();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/core/revocation/ocsp.ts
|
|
505
|
+
/**
|
|
506
|
+
* OID for Authority Information Access extension
|
|
507
|
+
*/
|
|
508
|
+
const id_pe_authorityInfoAccess = "1.3.6.1.5.5.7.1.1";
|
|
509
|
+
/**
|
|
510
|
+
* SHA-1 algorithm identifier for OCSP
|
|
511
|
+
*/
|
|
512
|
+
const SHA1_OID = "1.3.14.3.2.26";
|
|
513
|
+
/**
|
|
514
|
+
* Compute SHA-1 hash of data (cross-platform)
|
|
515
|
+
*/
|
|
516
|
+
async function computeSHA1(data) {
|
|
517
|
+
if (typeof crypto !== "undefined" && crypto.subtle) {
|
|
518
|
+
return crypto.subtle.digest("SHA-1", data);
|
|
519
|
+
}
|
|
520
|
+
// Node.js fallback
|
|
521
|
+
const nodeCrypto = require("crypto");
|
|
522
|
+
const hash = nodeCrypto.createHash("sha1");
|
|
523
|
+
hash.update(Buffer.from(data));
|
|
524
|
+
return hash.digest().buffer;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Extract OCSP responder URLs from certificate
|
|
528
|
+
* @param cert X509Certificate to extract OCSP URLs from
|
|
529
|
+
* @returns Array of OCSP responder URLs
|
|
530
|
+
*/
|
|
531
|
+
function extractOCSPUrls(cert) {
|
|
532
|
+
try {
|
|
533
|
+
const aiaExt = cert.getExtension(id_pe_authorityInfoAccess);
|
|
534
|
+
if (!aiaExt) {
|
|
535
|
+
return [];
|
|
536
|
+
}
|
|
537
|
+
// Get OCSP URLs from the extension
|
|
538
|
+
return aiaExt.ocsp.filter((gn) => gn.type === "url").map((gn) => gn.value);
|
|
539
|
+
}
|
|
540
|
+
catch {
|
|
541
|
+
return [];
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Extract CA Issuers URLs from certificate (for fetching issuer cert)
|
|
546
|
+
* @param cert X509Certificate to extract URLs from
|
|
547
|
+
* @returns Array of CA Issuers URLs
|
|
548
|
+
*/
|
|
549
|
+
function extractCAIssuersUrls(cert) {
|
|
550
|
+
try {
|
|
551
|
+
const aiaExt = cert.getExtension(id_pe_authorityInfoAccess);
|
|
552
|
+
if (!aiaExt) {
|
|
553
|
+
return [];
|
|
554
|
+
}
|
|
555
|
+
return aiaExt.caIssuers.filter((gn) => gn.type === "url").map((gn) => gn.value);
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
return [];
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Find issuer certificate from certificate chain
|
|
563
|
+
* @param cert Certificate to find issuer for
|
|
564
|
+
* @param chain Array of PEM-formatted certificates
|
|
565
|
+
* @returns Issuer certificate or null if not found
|
|
566
|
+
*/
|
|
567
|
+
function findIssuerInChain(cert, chain) {
|
|
568
|
+
const issuerName = cert.issuer;
|
|
569
|
+
for (const pemCert of chain) {
|
|
570
|
+
try {
|
|
571
|
+
const chainCert = new x509.X509Certificate(pemCert);
|
|
572
|
+
// Check if this cert's subject matches our cert's issuer
|
|
573
|
+
if (chainCert.subject === issuerName) {
|
|
574
|
+
return chainCert;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
// Skip invalid certificates
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Fetch issuer certificate from AIA extension
|
|
585
|
+
* @param cert Certificate to fetch issuer for
|
|
586
|
+
* @param timeout Timeout in ms
|
|
587
|
+
* @param proxyUrl Optional CORS proxy URL
|
|
588
|
+
* @returns Issuer certificate or null
|
|
589
|
+
*/
|
|
590
|
+
async function fetchIssuerFromAIA(cert, timeout = 5000, proxyUrl) {
|
|
591
|
+
const urls = extractCAIssuersUrls(cert);
|
|
592
|
+
for (const url of urls) {
|
|
593
|
+
try {
|
|
594
|
+
const result = await normalize.fetchIssuerCertificate(url, timeout, proxyUrl);
|
|
595
|
+
if (result.ok && result.data) {
|
|
596
|
+
// Try to parse as DER first, then PEM
|
|
597
|
+
try {
|
|
598
|
+
return new x509.X509Certificate(result.data);
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
// Try converting to PEM
|
|
602
|
+
const pem = normalize.arrayBufferToPEM(result.data);
|
|
603
|
+
return new x509.X509Certificate(pem);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
// Try next URL
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Build OCSP request for a certificate
|
|
615
|
+
* @param cert Certificate to check
|
|
616
|
+
* @param issuerCert Issuer certificate
|
|
617
|
+
* @returns DER-encoded OCSP request
|
|
618
|
+
*/
|
|
619
|
+
async function buildOCSPRequest(cert, issuerCert) {
|
|
620
|
+
// Get issuer name hash (SHA-1 of issuer's DN in DER)
|
|
621
|
+
// Parse the raw certificate to get the proper ASN.1 structures for serialization
|
|
622
|
+
const issuerCertAsn = AsnParser.parse(issuerCert.rawData, normalize.Certificate);
|
|
623
|
+
const issuerNameDer = AsnConvert.serialize(issuerCertAsn.tbsCertificate.subject);
|
|
624
|
+
const issuerNameHash = await computeSHA1(issuerNameDer);
|
|
625
|
+
// Get issuer key hash (SHA-1 of issuer's public key BIT STRING value, not the full SPKI)
|
|
626
|
+
const issuerKeyHash = await computeSHA1(issuerCertAsn.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey);
|
|
627
|
+
// Get certificate serial number
|
|
628
|
+
const serialNumber = normalize.hexToArrayBuffer(cert.serialNumber);
|
|
629
|
+
// Build CertID
|
|
630
|
+
const certId = new normalize.CertID({
|
|
631
|
+
hashAlgorithm: new normalize.AlgorithmIdentifier({ algorithm: SHA1_OID }),
|
|
632
|
+
issuerNameHash: new normalize.OctetString(issuerNameHash),
|
|
633
|
+
issuerKeyHash: new normalize.OctetString(issuerKeyHash),
|
|
634
|
+
serialNumber: serialNumber,
|
|
635
|
+
});
|
|
636
|
+
// Build request
|
|
637
|
+
const request = new normalize.Request({ reqCert: certId });
|
|
638
|
+
// Build TBS request
|
|
639
|
+
const tbsRequest = new normalize.TBSRequest({
|
|
640
|
+
requestList: [request],
|
|
641
|
+
});
|
|
642
|
+
// Build OCSP request
|
|
643
|
+
const ocspRequest = new normalize.OCSPRequest({ tbsRequest });
|
|
644
|
+
return AsnConvert.serialize(ocspRequest);
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Parse OCSP response and extract revocation status
|
|
648
|
+
* @param responseData DER-encoded OCSP response
|
|
649
|
+
* @returns Revocation result
|
|
650
|
+
*/
|
|
651
|
+
function parseOCSPResponse(responseData) {
|
|
652
|
+
const now = new Date();
|
|
653
|
+
try {
|
|
654
|
+
const response = AsnConvert.parse(responseData, normalize.OCSPResponse);
|
|
655
|
+
// Check response status
|
|
656
|
+
switch (response.responseStatus) {
|
|
657
|
+
case normalize.OCSPResponseStatus.successful:
|
|
658
|
+
break;
|
|
659
|
+
case normalize.OCSPResponseStatus.malformedRequest:
|
|
660
|
+
return {
|
|
661
|
+
isValid: false,
|
|
662
|
+
status: "error",
|
|
663
|
+
method: "ocsp",
|
|
664
|
+
reason: "OCSP responder returned: malformed request",
|
|
665
|
+
checkedAt: now,
|
|
666
|
+
};
|
|
667
|
+
case normalize.OCSPResponseStatus.internalError:
|
|
668
|
+
return {
|
|
669
|
+
isValid: false,
|
|
670
|
+
status: "error",
|
|
671
|
+
method: "ocsp",
|
|
672
|
+
reason: "OCSP responder returned: internal error",
|
|
673
|
+
checkedAt: now,
|
|
674
|
+
};
|
|
675
|
+
case normalize.OCSPResponseStatus.tryLater:
|
|
676
|
+
return {
|
|
677
|
+
isValid: false,
|
|
678
|
+
status: "unknown",
|
|
679
|
+
method: "ocsp",
|
|
680
|
+
reason: "OCSP responder returned: try later",
|
|
681
|
+
checkedAt: now,
|
|
682
|
+
};
|
|
683
|
+
case normalize.OCSPResponseStatus.sigRequired:
|
|
684
|
+
return {
|
|
685
|
+
isValid: false,
|
|
686
|
+
status: "error",
|
|
687
|
+
method: "ocsp",
|
|
688
|
+
reason: "OCSP responder requires signature",
|
|
689
|
+
checkedAt: now,
|
|
690
|
+
};
|
|
691
|
+
case normalize.OCSPResponseStatus.unauthorized:
|
|
692
|
+
return {
|
|
693
|
+
isValid: false,
|
|
694
|
+
status: "error",
|
|
695
|
+
method: "ocsp",
|
|
696
|
+
reason: "OCSP responder returned: unauthorized",
|
|
697
|
+
checkedAt: now,
|
|
698
|
+
};
|
|
699
|
+
default:
|
|
700
|
+
return {
|
|
701
|
+
isValid: false,
|
|
702
|
+
status: "error",
|
|
703
|
+
method: "ocsp",
|
|
704
|
+
reason: `OCSP responder returned unknown status: ${response.responseStatus}`,
|
|
705
|
+
checkedAt: now,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
// Parse response bytes
|
|
709
|
+
if (!response.responseBytes) {
|
|
710
|
+
return {
|
|
711
|
+
isValid: false,
|
|
712
|
+
status: "error",
|
|
713
|
+
method: "ocsp",
|
|
714
|
+
reason: "OCSP response has no response bytes",
|
|
715
|
+
checkedAt: now,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
// Parse BasicOCSPResponse
|
|
719
|
+
const basicResponse = AsnConvert.parse(response.responseBytes.response.buffer, normalize.BasicOCSPResponse);
|
|
720
|
+
// Get the first single response
|
|
721
|
+
const responses = basicResponse.tbsResponseData.responses;
|
|
722
|
+
if (!responses || responses.length === 0) {
|
|
723
|
+
return {
|
|
724
|
+
isValid: false,
|
|
725
|
+
status: "error",
|
|
726
|
+
method: "ocsp",
|
|
727
|
+
reason: "OCSP response contains no certificate status",
|
|
728
|
+
checkedAt: now,
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
const singleResponse = responses[0];
|
|
732
|
+
const certStatus = singleResponse.certStatus;
|
|
733
|
+
// Check certificate status
|
|
734
|
+
if (certStatus.good !== undefined) {
|
|
735
|
+
return {
|
|
736
|
+
isValid: true,
|
|
737
|
+
status: "good",
|
|
738
|
+
method: "ocsp",
|
|
739
|
+
checkedAt: now,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
else if (certStatus.revoked) {
|
|
743
|
+
return {
|
|
744
|
+
isValid: false,
|
|
745
|
+
status: "revoked",
|
|
746
|
+
method: "ocsp",
|
|
747
|
+
reason: certStatus.revoked.revocationReason !== undefined
|
|
748
|
+
? `Certificate revoked (reason: ${certStatus.revoked.revocationReason})`
|
|
749
|
+
: "Certificate revoked",
|
|
750
|
+
revokedAt: certStatus.revoked.revocationTime,
|
|
751
|
+
checkedAt: now,
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
else if (certStatus.unknown !== undefined) {
|
|
755
|
+
return {
|
|
756
|
+
isValid: false,
|
|
757
|
+
status: "unknown",
|
|
758
|
+
method: "ocsp",
|
|
759
|
+
reason: "OCSP responder does not know about this certificate",
|
|
760
|
+
checkedAt: now,
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
isValid: false,
|
|
765
|
+
status: "error",
|
|
766
|
+
method: "ocsp",
|
|
767
|
+
reason: "Unexpected certificate status in OCSP response",
|
|
768
|
+
checkedAt: now,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
catch (error) {
|
|
772
|
+
return {
|
|
773
|
+
isValid: false,
|
|
774
|
+
status: "error",
|
|
775
|
+
method: "ocsp",
|
|
776
|
+
reason: `Failed to parse OCSP response: ${error instanceof Error ? error.message : String(error)}`,
|
|
777
|
+
checkedAt: now,
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Check certificate revocation via OCSP
|
|
783
|
+
* @param cert Certificate to check
|
|
784
|
+
* @param issuerCert Issuer certificate (optional, will try to find/fetch)
|
|
785
|
+
* @param options OCSP check options
|
|
786
|
+
* @returns Revocation result
|
|
787
|
+
*/
|
|
788
|
+
async function checkOCSP(cert, issuerCert, options = {}) {
|
|
789
|
+
const { timeout = 5000, certificateChain = [], proxyUrl } = options;
|
|
790
|
+
const now = new Date();
|
|
791
|
+
// Get OCSP URLs
|
|
792
|
+
const ocspUrls = extractOCSPUrls(cert);
|
|
793
|
+
if (ocspUrls.length === 0) {
|
|
794
|
+
return {
|
|
795
|
+
isValid: false,
|
|
796
|
+
status: "unknown",
|
|
797
|
+
method: "ocsp",
|
|
798
|
+
reason: "Certificate has no OCSP responder URL",
|
|
799
|
+
checkedAt: now,
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
// Try to find issuer certificate
|
|
803
|
+
let issuer = issuerCert;
|
|
804
|
+
if (!issuer) {
|
|
805
|
+
// Try certificate chain first
|
|
806
|
+
issuer = findIssuerInChain(cert, certificateChain);
|
|
807
|
+
}
|
|
808
|
+
if (!issuer) {
|
|
809
|
+
// Try AIA extension
|
|
810
|
+
issuer = await fetchIssuerFromAIA(cert, timeout, proxyUrl);
|
|
811
|
+
}
|
|
812
|
+
if (!issuer) {
|
|
813
|
+
return {
|
|
814
|
+
isValid: false,
|
|
815
|
+
status: "unknown",
|
|
816
|
+
method: "ocsp",
|
|
817
|
+
reason: "Could not find or fetch issuer certificate for OCSP",
|
|
818
|
+
checkedAt: now,
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
// Build OCSP request
|
|
822
|
+
let request;
|
|
823
|
+
try {
|
|
824
|
+
request = await buildOCSPRequest(cert, issuer);
|
|
825
|
+
}
|
|
826
|
+
catch (error) {
|
|
827
|
+
return {
|
|
828
|
+
isValid: false,
|
|
829
|
+
status: "error",
|
|
830
|
+
method: "ocsp",
|
|
831
|
+
reason: `Failed to build OCSP request: ${error instanceof Error ? error.message : String(error)}`,
|
|
832
|
+
checkedAt: now,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
// Try each OCSP URL
|
|
836
|
+
for (const url of ocspUrls) {
|
|
837
|
+
try {
|
|
838
|
+
const result = await normalize.fetchOCSP(url, request, timeout, proxyUrl);
|
|
839
|
+
if (result.ok && result.data) {
|
|
840
|
+
return parseOCSPResponse(result.data);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
catch {
|
|
844
|
+
// Try next URL
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return {
|
|
848
|
+
isValid: false,
|
|
849
|
+
status: "error",
|
|
850
|
+
method: "ocsp",
|
|
851
|
+
reason: "All OCSP requests failed",
|
|
852
|
+
checkedAt: now,
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const AUTHORITY_KEY_IDENTIFIER_OID = "2.5.29.35";
|
|
857
|
+
const SUBJECT_KEY_IDENTIFIER_OID = "2.5.29.14";
|
|
858
|
+
async function computeSha256Hex(input) {
|
|
859
|
+
const digest = await crypto.subtle.digest("SHA-256", input);
|
|
860
|
+
return normalize.arrayBufferToHex(digest);
|
|
861
|
+
}
|
|
862
|
+
function getAuthorityKeyIdentifierHex(certificate) {
|
|
863
|
+
const authorityKeyIdentifier = certificate.getExtension(AUTHORITY_KEY_IDENTIFIER_OID);
|
|
864
|
+
return normalize.normalizeKeyIdentifier(authorityKeyIdentifier?.keyId);
|
|
865
|
+
}
|
|
866
|
+
function getSubjectKeyIdentifierHex(certificate) {
|
|
867
|
+
const subjectKeyIdentifier = certificate.getExtension(SUBJECT_KEY_IDENTIFIER_OID);
|
|
868
|
+
return normalize.normalizeKeyIdentifier(subjectKeyIdentifier?.keyId);
|
|
869
|
+
}
|
|
870
|
+
async function extractIssuerIdentityFromCertificate(certificatePem, options = {}) {
|
|
871
|
+
const signerCertificate = new x509.X509Certificate(certificatePem);
|
|
872
|
+
let issuerCertificate = options.certificateChain && options.certificateChain.length > 0
|
|
873
|
+
? findIssuerInChain(signerCertificate, options.certificateChain)
|
|
874
|
+
: null;
|
|
875
|
+
if (!issuerCertificate && options.fetchOptions) {
|
|
876
|
+
issuerCertificate = await fetchIssuerFromAIA(signerCertificate, options.fetchOptions.timeout, options.fetchOptions.proxyUrl);
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
issuerSubjectDn: normalize.normalizeDistinguishedName(signerCertificate.issuer),
|
|
880
|
+
authorityKeyIdentifierHex: getAuthorityKeyIdentifierHex(signerCertificate),
|
|
881
|
+
issuerCertificate: issuerCertificate
|
|
882
|
+
? {
|
|
883
|
+
subjectDn: normalize.normalizeDistinguishedName(issuerCertificate.subject),
|
|
884
|
+
spkiSha256Hex: await computeSha256Hex(issuerCertificate.publicKey.rawData),
|
|
885
|
+
}
|
|
886
|
+
: null,
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
async function extractCertificateIdentityFromCertificate(certificatePem) {
|
|
890
|
+
const certificate = new x509.X509Certificate(certificatePem);
|
|
891
|
+
return {
|
|
892
|
+
subjectDn: normalize.normalizeDistinguishedName(certificate.subject),
|
|
893
|
+
subjectKeyIdentifierHex: getSubjectKeyIdentifierHex(certificate),
|
|
894
|
+
spkiSha256Hex: await computeSha256Hex(certificate.publicKey.rawData),
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
exports.AsnConvert = AsnConvert;
|
|
899
|
+
exports.checkOCSP = checkOCSP;
|
|
900
|
+
exports.extractCertificateIdentityFromCertificate = extractCertificateIdentityFromCertificate;
|
|
901
|
+
exports.extractIssuerIdentityFromCertificate = extractIssuerIdentityFromCertificate;
|
|
902
|
+
//# sourceMappingURL=identity-1a3dddc3.js.map
|