@gwydion-id/icao-9303-models 0.1.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/LICENSES/CC0-1.0.txt +121 -0
- package/LICENSES/LGPL-3.0-or-later.txt +304 -0
- package/README.md +79 -0
- package/package.json +17 -0
- package/package.json.license +3 -0
- package/src/digitalseal.js +234 -0
- package/src/digitalsealv3.js +359 -0
- package/src/digitalsealv4.js +369 -0
- package/src/index.js +41 -0
- package/src/mrvadocument.js +465 -0
- package/src/mrvadocumentwithvds3.js +559 -0
- package/src/mrvadocumentwithvds4.js +559 -0
- package/src/mrvbdocument.js +466 -0
- package/src/mrvbdocumentwithvds3.js +560 -0
- package/src/mrvbdocumentwithvds4.js +560 -0
- package/src/td1document.js +408 -0
- package/src/td1documentwithvds3.js +479 -0
- package/src/td1documentwithvds4.js +479 -0
- package/src/td2document.js +379 -0
- package/src/td2documentwithvds3.js +466 -0
- package/src/td2documentwithvds4.js +466 -0
- package/src/td3document.js +391 -0
- package/src/td3documentwithvds3.js +466 -0
- package/src/td3documentwithvds4.js +466 -0
- package/src/traveldocument.js +306 -0
- package/src/utilities/bytes-to-date.js +23 -0
- package/src/utilities/c40-decode.js +225 -0
- package/src/utilities/c40-encode.js +254 -0
- package/src/utilities/c40-shift1.js +10 -0
- package/src/utilities/date-to-bytes.js +38 -0
- package/src/utilities/der-length-to-length.js +33 -0
- package/src/utilities/expand-year.js +26 -0
- package/src/utilities/gender-marker-to-mrz.js +14 -0
- package/src/utilities/generate-mrz-check-digit.js +139 -0
- package/src/utilities/icao-date.js +234 -0
- package/src/utilities/length-to-der-length.js +41 -0
- package/src/utilities/nationality-codes.js +303 -0
- package/src/utilities/normalize-mrz-string.js +19 -0
- package/src/utilities/optional-data-mrz.js +29 -0
- package/src/utilities/pad-mrz-string.js +16 -0
- package/src/utilities/set-signature-zone.js +34 -0
- package/src/utilities/stringify-validation-array.js +27 -0
- package/src/utilities/validate-hex-string.js +41 -0
- package/src/utilities/validate-identifier-code.js +38 -0
- package/src/utilities/validate-mrz-string.js +44 -0
- package/src/utilities/vds-magic.js +9 -0
- package/src/utilities/vds-signature-marker.js +9 -0
- package/src/utilities/vds-version-3.js +9 -0
- package/src/utilities/vds-version-4.js +9 -0
- package/src/visadocument.js +114 -0
- package/tests/models/digitalsealv4.test.js +67 -0
- package/tests/models/mrvadocument.test.js +61 -0
- package/tests/models/mrvbdocument.test.js +61 -0
- package/tests/models/td1document.test.js +50 -0
- package/tests/models/td2document.test.js +49 -0
- package/tests/models/td3document.test.js +88 -0
- package/tests/utilities/c40-encodation-scheme.test.js +34 -0
- package/tests/utilities/der-tlv-length-conversion.test.js +99 -0
- package/tests/utilities/document-utilities.test.js +199 -0
- package/tests/utilities/icao-constants.test.js +35 -0
- package/tests/utilities/icao-date.test.js +141 -0
- package/tests/utilities/vds-date-conversions.test.js +28 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Don Geronimo <https://sentamal.in>
|
|
2
|
+
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
import { VDS_SIGNATURE_MARKER } from "./utilities/vds-signature-marker.js";
|
|
5
|
+
import { lengthToDERLength } from "./utilities/length-to-der-length.js";
|
|
6
|
+
import { setSignatureZone } from "./utilities/set-signature-zone.js";
|
|
7
|
+
import { validateMRZString } from "./utilities/validate-mrz-string.js"
|
|
8
|
+
import { validateIdentifierCode } from "./utilities/validate-identifier-code.js";
|
|
9
|
+
import { validateHexString } from "./utilities/validate-hex-string.js";
|
|
10
|
+
import { IcaoDate } from "./utilities/icao-date.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Stores common properties and methods for all ICAO 9303 visible digital seals
|
|
14
|
+
* (VDSs).
|
|
15
|
+
*
|
|
16
|
+
* `DigitalSeal` is intended to be used to compose different VDS versions. It is
|
|
17
|
+
* not intended to be instantiated directly.
|
|
18
|
+
*/
|
|
19
|
+
export class DigitalSeal {
|
|
20
|
+
/**
|
|
21
|
+
* Create a `DigitalSeal`.
|
|
22
|
+
* @param { Object } [opt] - An options object.
|
|
23
|
+
* @param { string } [opt.authorityCode] - A 3-character string consisting of
|
|
24
|
+
* the characters A-Z, 0-9, ' ', or <. A code from ISO-3166-1,
|
|
25
|
+
* ICAO 9303-3, or these user-assigned ranges are recommended: AAA-AAZ,
|
|
26
|
+
* QMA-QZZ, XAA-XZZ, or ZZA-ZZZ.
|
|
27
|
+
* @param { string } [opt.identifierCode] - A 4-character string consisting of
|
|
28
|
+
* the characters 0-9 and A-Z.
|
|
29
|
+
* @param { string } [opt.certReference] - A hex-string that uniquely
|
|
30
|
+
* identifies a certificate for a given signer.
|
|
31
|
+
* @param { string | Date | IcaoDate } [opt.issueDate] - A calendar date string in
|
|
32
|
+
* YYYY-MM-DD format, a `Date` object, or an `IcaoDate` object.
|
|
33
|
+
* @param { string | Date | IcaoDate } [opt.signatureDate] - A calendar date string in
|
|
34
|
+
* YYYY-MM-DD format, a `Date` object, or an `IcaoDate` object.
|
|
35
|
+
* @param { number } [opt.featureDefinition] - A number in the range of
|
|
36
|
+
* 0x01-0xFE.
|
|
37
|
+
* @param { number } [opt.typeCategory] - A number in the range of 0x01-0xFE.
|
|
38
|
+
* Odd numbers in the range between 0x01 and 0xFD shall be used for
|
|
39
|
+
* ICAO-specified document type categories.
|
|
40
|
+
* @param { Map<number, number[]> } [opt.features] - A store of
|
|
41
|
+
* digitally-encoded document features used to create the message zone.
|
|
42
|
+
* @param { number[] } [opt.signatureData] - The raw signature data generated
|
|
43
|
+
* by concatenating the header and message zone, hashing the result, and
|
|
44
|
+
* signing the hash with a cryptographic key.
|
|
45
|
+
* @param { number[] } [opt.signatureZone] - The signature zone of the VDS as
|
|
46
|
+
* a TLV of the signature marker, its length in BER/DER definite length
|
|
47
|
+
* form, and the raw signature data.
|
|
48
|
+
*/
|
|
49
|
+
constructor(opt) {
|
|
50
|
+
this.authorityCode = opt?.authorityCode ?? "UTO";
|
|
51
|
+
this.identifierCode = opt?.identifierCode ?? "UTSS";
|
|
52
|
+
this.certReference = opt?.certReference ?? "00000";
|
|
53
|
+
this.issueDate = opt?.issueDate ?? "2007-04-15";
|
|
54
|
+
this.signatureDate = opt?.signatureDate ?? "2007-04-15";
|
|
55
|
+
this.featureDefinition = opt?.featureDefinition ?? 0x01;
|
|
56
|
+
this.typeCategory = opt?.typeCategory ?? 0x01;
|
|
57
|
+
this.features = opt?.features ?? new Map();
|
|
58
|
+
this.signatureData = opt?.signatureData ?? Array(64).fill(0);
|
|
59
|
+
|
|
60
|
+
if (opt?.signatureZone) { this.signatureZone = opt.signatureZone; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#authorityCode;
|
|
64
|
+
/**
|
|
65
|
+
* A code identifying the authority who issued this seal.
|
|
66
|
+
* @type { string }
|
|
67
|
+
*/
|
|
68
|
+
get authorityCode() { return this.#authorityCode; }
|
|
69
|
+
/**
|
|
70
|
+
* @param { string } value - A 3-character string consisting of the characters
|
|
71
|
+
* A-Z, 0-9, ' ', or <. A code from ISO-3166-1, ICAO 9303-3, or these
|
|
72
|
+
* user-assigned ranges are recommended: AAA-AAZ, QMA-QZZ, XAA-XZZ, or
|
|
73
|
+
* ZZA-ZZZ.
|
|
74
|
+
*/
|
|
75
|
+
set authorityCode(value) {
|
|
76
|
+
const isInvalid = validateMRZString(value, {
|
|
77
|
+
minimum: 1,
|
|
78
|
+
maximum: 3
|
|
79
|
+
});
|
|
80
|
+
if (isInvalid) {
|
|
81
|
+
throw new RangeError(
|
|
82
|
+
`Value set on 'authorityCode' has errors: ${isInvalid}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
this.#authorityCode = value.toUpperCase();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#identifierCode;
|
|
89
|
+
/**
|
|
90
|
+
* A combination of a two-letter authority code and of two alphanumeric
|
|
91
|
+
* characters to identify a signer within the issuing authority.
|
|
92
|
+
* @type { string }
|
|
93
|
+
*/
|
|
94
|
+
get identifierCode() { return this.#identifierCode; }
|
|
95
|
+
/**
|
|
96
|
+
* @param { string } value - A 4-character string consisting of the characters
|
|
97
|
+
* 0-9 and A-Z.
|
|
98
|
+
*/
|
|
99
|
+
set identifierCode(value) {
|
|
100
|
+
const isInvalid = validateIdentifierCode(value);
|
|
101
|
+
if (isInvalid) {
|
|
102
|
+
throw new RangeError(
|
|
103
|
+
`Value set on 'identifierCode' has errors: ${isInvalid}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
this.#identifierCode = value.toUpperCase();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#certReference;
|
|
110
|
+
/**
|
|
111
|
+
* A hex-string that uniquely identifies a certificate for a given signer.
|
|
112
|
+
* @type { string }
|
|
113
|
+
*/
|
|
114
|
+
get certReference() { return this.#certReference; }
|
|
115
|
+
/**
|
|
116
|
+
* @param { string } value - A hexadecimal string.
|
|
117
|
+
*/
|
|
118
|
+
set certReference(value) {
|
|
119
|
+
const isInvalid = validateHexString(value);
|
|
120
|
+
if (isInvalid) {
|
|
121
|
+
throw new RangeError(
|
|
122
|
+
`Value set on 'certReference' has errors: ${isInvalid}`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
this.#certReference = value.toUpperCase();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#issueDate;
|
|
129
|
+
/**
|
|
130
|
+
* A date string on which the document was issued.
|
|
131
|
+
* @type { IcaoDate }
|
|
132
|
+
*/
|
|
133
|
+
get issueDate() { return this.#issueDate; }
|
|
134
|
+
/**
|
|
135
|
+
* @param { string | Date | IcaoDate } value - A calendar date string in YYYY-MM-DD
|
|
136
|
+
* format, a `Date` object, or an `IcaoDate` object.
|
|
137
|
+
*/
|
|
138
|
+
set issueDate(value) {
|
|
139
|
+
this.#issueDate = new IcaoDate(value);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#signatureDate;
|
|
143
|
+
/**
|
|
144
|
+
* A date string on which the seal was signed.
|
|
145
|
+
* @type { IcaoDate }
|
|
146
|
+
*/
|
|
147
|
+
get signatureDate() { return this.#signatureDate; }
|
|
148
|
+
/**
|
|
149
|
+
* @param { string | Date | IcaoDate } value - A calendar date string in YYYY-MM-DD
|
|
150
|
+
* format or a `Date` object.
|
|
151
|
+
*/
|
|
152
|
+
set signatureDate(value) {
|
|
153
|
+
this.#signatureDate = new IcaoDate(value);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#featureDefinition;
|
|
157
|
+
/**
|
|
158
|
+
* A reference code to a document that defines the number and encoding of VDS
|
|
159
|
+
* features.
|
|
160
|
+
* @type { number }
|
|
161
|
+
*/
|
|
162
|
+
get featureDefinition() { return this.#featureDefinition; }
|
|
163
|
+
/**
|
|
164
|
+
* @param { number } value - A number in the range of 0x01-0xFE.
|
|
165
|
+
*/
|
|
166
|
+
set featureDefinition(value) {
|
|
167
|
+
if (value <= 0 || value > 254) {
|
|
168
|
+
throw new RangeError(
|
|
169
|
+
"Document feature definition reference must be in the range between " +
|
|
170
|
+
"1-254."
|
|
171
|
+
);
|
|
172
|
+
} else {
|
|
173
|
+
this.#featureDefinition = value;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#typeCategory;
|
|
178
|
+
/**
|
|
179
|
+
* A code defining the document type category to which the seal is attached.
|
|
180
|
+
* @type { number }
|
|
181
|
+
*/
|
|
182
|
+
get typeCategory() { return this.#typeCategory; }
|
|
183
|
+
/**
|
|
184
|
+
* @param { number } value - A number in the range of 0x01 - 0xFE. Odd numbers
|
|
185
|
+
* in the range between 0x01 and 0xFD shall be used for ICAO-specified
|
|
186
|
+
* document type categories.
|
|
187
|
+
*/
|
|
188
|
+
set typeCategory(value) {
|
|
189
|
+
if (value <= 0 || value > 254) {
|
|
190
|
+
throw new RangeError(
|
|
191
|
+
"Document type category must be in the range between 1-254."
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
this.#typeCategory = value;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* A store of digitally-encoded document features used to create the message
|
|
200
|
+
* zone.
|
|
201
|
+
* @type { Map<number, number[]>}
|
|
202
|
+
*/
|
|
203
|
+
features;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The raw signature data generated by concatenating the header and message
|
|
207
|
+
* zone, hashing the result, and signing the hash with a cryptographic
|
|
208
|
+
* key.
|
|
209
|
+
* @type { number[] }
|
|
210
|
+
*/
|
|
211
|
+
signatureData;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The signature zone of the VDS as a TLV of the signature marker, its length
|
|
215
|
+
* in BER/DER definite length form, and the raw signature data.
|
|
216
|
+
* @type { number[] }
|
|
217
|
+
*/
|
|
218
|
+
get signatureZone() {
|
|
219
|
+
let output = [];
|
|
220
|
+
output.push(VDS_SIGNATURE_MARKER);
|
|
221
|
+
output = output.concat(
|
|
222
|
+
lengthToDERLength(this.signatureData.length)
|
|
223
|
+
);
|
|
224
|
+
output = output.concat(this.signatureData);
|
|
225
|
+
return output;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* @param { number[] } value
|
|
229
|
+
*/
|
|
230
|
+
set signatureZone(value) {
|
|
231
|
+
this.signature = setSignatureZone(0, value);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Don Geronimo <https://sentamal.in>
|
|
2
|
+
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
import { DigitalSeal } from "./digitalseal.js";
|
|
5
|
+
import { c40Encode } from "./utilities/c40-encode.js";
|
|
6
|
+
import { c40Decode } from "./utilities/c40-decode.js";
|
|
7
|
+
import { VDS_MAGIC } from "./utilities/vds-magic.js";
|
|
8
|
+
import { VDS_SIGNATURE_MARKER } from "./utilities/vds-signature-marker.js";
|
|
9
|
+
import { setSignatureZone } from "./utilities/set-signature-zone.js";
|
|
10
|
+
import { VDS_VERSION_3 } from "./utilities/vds-version-3.js";
|
|
11
|
+
import { IcaoDate } from "./utilities/icao-date.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Stores properties and methods for ICAO 9303 visible digital seals (VDSs)
|
|
15
|
+
* version 3. Consult ICAO 9030 part 13 to see the differences between this
|
|
16
|
+
* version and other versions. Use of version 3 VDSs are discouraged but may
|
|
17
|
+
* still be encountered in the wild.
|
|
18
|
+
*
|
|
19
|
+
* `DigitalSealV3` is intended to be used to compose different kinds of
|
|
20
|
+
* machine-readable travel documents (MRTDs) with attached VDSs containing
|
|
21
|
+
* different document features. It is not intended to be instantiated
|
|
22
|
+
* directly.
|
|
23
|
+
*/
|
|
24
|
+
export class DigitalSealV3 {
|
|
25
|
+
/**
|
|
26
|
+
* Create a `DigitalSealV3`.
|
|
27
|
+
* @param { Object } [opt] - An options object.
|
|
28
|
+
* @param { string } [opt.authorityCode] - A 3-character string consisting of
|
|
29
|
+
* the characters A-Z, 0-9, ' ', or <. A code from ISO-3166-1,
|
|
30
|
+
* ICAO 9303-3, or these user-assigned ranges are recommended: AAA-AAZ,
|
|
31
|
+
* QMA-QZZ, XAA-XZZ, or ZZA-ZZZ.
|
|
32
|
+
* @param { string } [opt.identifierCode] - A 4-character string consisting of
|
|
33
|
+
* the characters 0-9 and A-Z.
|
|
34
|
+
* @param { string } [opt.certReference] - A hex-string that uniquely
|
|
35
|
+
* identifies a certificate for a given signer.
|
|
36
|
+
* @param { string | Date | IcaoDate } [opt.issueDate] - A calendar date string in
|
|
37
|
+
* YYYY-MM-DD format, a `Date` object, or an `IcaoDate` object.
|
|
38
|
+
* @param { string | Date | IcaoDate } [opt.signatureDate] - A calendar date string in
|
|
39
|
+
* YYYY-MM-DD format, a `Date` object, or an `IcaoDate` object.
|
|
40
|
+
* @param { number } [opt.featureDefinition] - A number in the range of
|
|
41
|
+
* 0x01-0xFE.
|
|
42
|
+
* @param { number } [opt.typeCategory] - A number in the range of 0x01-0xFE.
|
|
43
|
+
* Odd numbers in the range between 0x01 and 0xFD shall be used for
|
|
44
|
+
* ICAO-specified document type categories.
|
|
45
|
+
* @param { Map<number, number[]> } [opt.features] - A store of
|
|
46
|
+
* digitally-encoded document features used to create the message zone.
|
|
47
|
+
* @param { number[] } [opt.signatureData] - The raw signature data generated
|
|
48
|
+
* by concatenating the header and message zone, hashing the result, and
|
|
49
|
+
* signing the hash with a cryptographic key.
|
|
50
|
+
* @param { number[] } [opt.headerZone] - The header zone of the VDS as
|
|
51
|
+
* defined by ICAO 9303 part 13.
|
|
52
|
+
* @param { number[] } [opt.messageZone] - The message zone of the VDS as
|
|
53
|
+
* defined by ICAO 9303 part 13.
|
|
54
|
+
* @param { number[] } [opt.signatureZone] - The signature zone of the VDS as
|
|
55
|
+
* a TLV of the signature marker, its length in BER/DER definite length
|
|
56
|
+
* form, and the raw signature data.
|
|
57
|
+
* @param { number[] } [opt.unsignedSeal] - A concatenation of the header zone
|
|
58
|
+
* and the message zone of the VDS.
|
|
59
|
+
* @param { number[] } [opt.signedSeal] - A concatenation of the header zone,
|
|
60
|
+
* the message zone, and the signature zone of the VDS.
|
|
61
|
+
*/
|
|
62
|
+
constructor(opt) {
|
|
63
|
+
this.#digitalseal = new DigitalSeal();
|
|
64
|
+
|
|
65
|
+
this.authorityCode = opt?.authorityCode ?? "UTO";
|
|
66
|
+
this.identifierCode = opt?.identifierCode ?? "UTSS";
|
|
67
|
+
this.certReference = opt?.certReference ?? "00000";
|
|
68
|
+
this.issueDate = opt?.issueDate ?? "2007-04-15";
|
|
69
|
+
this.signatureDate = opt?.signatureDate ?? "2007-04-15";
|
|
70
|
+
this.featureDefinition = opt?.featureDefinition ?? 0x01;
|
|
71
|
+
this.typeCategory = opt?.typeCategory ?? 0x01;
|
|
72
|
+
this.features = opt?.features ?? new Map();
|
|
73
|
+
this.signatureData = opt?.signatureData ?? Array(64).fill(0);
|
|
74
|
+
|
|
75
|
+
if (opt?.headerZone) { this.headerZone = opt.headerZone; }
|
|
76
|
+
if (opt?.messageZone) { this.messageZone = opt.messageZone; }
|
|
77
|
+
if (opt?.signatureZone) { this.signatureZone = opt.signatureZone; }
|
|
78
|
+
if (opt?.unsignedSeal) { this.unsignedSeal = opt.unsignedSeal; }
|
|
79
|
+
if (opt?.signedSeal) { this.signedSeal = opt.signedSeal; }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// The object `DigitalSealV3` uses to compose itself.
|
|
83
|
+
#digitalseal;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A code identifying the authority who issued this seal.
|
|
87
|
+
* @type { string }
|
|
88
|
+
*/
|
|
89
|
+
get authorityCode() { return this.#digitalseal.authorityCode; }
|
|
90
|
+
/**
|
|
91
|
+
* @param { string } value - A 3-character string consisting of the characters
|
|
92
|
+
* A-Z, 0-9, ' ', or <. A code from ISO-3166-1, ICAO 9303-3, or these
|
|
93
|
+
* user-assigned ranges are recommended: AAA-AAZ, QMA-QZZ, XAA-XZZ, or ZZA-ZZZ.
|
|
94
|
+
*/
|
|
95
|
+
set authorityCode(value) { this.#digitalseal.authorityCode = value; }
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A combination of a two-letter authority code and of two alphanumeric
|
|
99
|
+
* characters to identify a signer within the issuing authority.
|
|
100
|
+
* @type { string }
|
|
101
|
+
*/
|
|
102
|
+
get identifierCode() { return this.#digitalseal.identifierCode; }
|
|
103
|
+
/**
|
|
104
|
+
* @param { string } value - A 4-character string consisting of the characters
|
|
105
|
+
* 0-9 and A-Z.
|
|
106
|
+
*/
|
|
107
|
+
set identifierCode(value) { this.#digitalseal.identifierCode = value; }
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A hex-string that uniquely identifies a certificate for a given signer.
|
|
111
|
+
* @type { string }
|
|
112
|
+
*/
|
|
113
|
+
get certReference() { return this.#digitalseal.certReference; }
|
|
114
|
+
/**
|
|
115
|
+
* @param { string } value - A hex-string of exactly 5 characters.
|
|
116
|
+
*/
|
|
117
|
+
set certReference(value) {
|
|
118
|
+
if (value.length !== 5) {
|
|
119
|
+
throw new RangeError(
|
|
120
|
+
"Certificate reference must be a hex string of exactly 5 characters."
|
|
121
|
+
);
|
|
122
|
+
} else {
|
|
123
|
+
this.#digitalseal.certReference = value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A date string on which the document was issued.
|
|
129
|
+
* @type { IcaoDate }
|
|
130
|
+
*/
|
|
131
|
+
get issueDate() { return this.#digitalseal.issueDate; }
|
|
132
|
+
/**
|
|
133
|
+
* @param { string | Date | IcaoDate } value - A calendar date string in YYYY-MM-DD
|
|
134
|
+
* format, a `Date` object, or an `IcaoDate` object.
|
|
135
|
+
*/
|
|
136
|
+
set issueDate(value) { this.#digitalseal.issueDate = value; }
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A date string on which the seal was signed.
|
|
140
|
+
* @type { IcaoDate }
|
|
141
|
+
*/
|
|
142
|
+
get signatureDate() { return this.#digitalseal.signatureDate; }
|
|
143
|
+
/**
|
|
144
|
+
* @param { string | Date | IcaoDate } value - A calendar date string in YYYY-MM-DD
|
|
145
|
+
* format, a `Date` object, or an `IcaoDate` object.
|
|
146
|
+
*/
|
|
147
|
+
set signatureDate(value) { this.#digitalseal.signatureDate = value; }
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* A reference code to a document that defines the number and encoding of VDS
|
|
151
|
+
* features.
|
|
152
|
+
* @type { number }
|
|
153
|
+
*/
|
|
154
|
+
get featureDefinition() { return this.#digitalseal.featureDefinition; }
|
|
155
|
+
/**
|
|
156
|
+
* @param { number } value - A number in the range of 0x01 - 0xFE.
|
|
157
|
+
*/
|
|
158
|
+
set featureDefinition(value) { this.#digitalseal.featureDefinition = value; }
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A code defining the document type category to which the seal is attached.
|
|
162
|
+
* @type { number }
|
|
163
|
+
*/
|
|
164
|
+
get typeCategory() { return this.#digitalseal.typeCategory; }
|
|
165
|
+
/**
|
|
166
|
+
* @param { number } value - A number in the range of 0x01-0xFE. Odd numbers
|
|
167
|
+
* in the range between 0x01 and 0xFD shall be used for ICAO-specified
|
|
168
|
+
* document type categories.
|
|
169
|
+
*/
|
|
170
|
+
set typeCategory(value) { this.#digitalseal.typeCategory = value; }
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* A store of digitally-encoded document features used to create the message
|
|
174
|
+
* zone.
|
|
175
|
+
* @type { Map<number, number[]>}
|
|
176
|
+
*/
|
|
177
|
+
get features() { return this.#digitalseal.features; }
|
|
178
|
+
/**
|
|
179
|
+
* @param { Map<number, number[]> } value
|
|
180
|
+
*/
|
|
181
|
+
set features(value) { this.#digitalseal.features = value; }
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The raw signature data generated by concatenating the header and message
|
|
185
|
+
* zone, hashing the result, and signing the hash with a cryptographic
|
|
186
|
+
* key.
|
|
187
|
+
* @type { number[] }
|
|
188
|
+
*/
|
|
189
|
+
get signatureData() { return this.#digitalseal.signatureData; }
|
|
190
|
+
/**
|
|
191
|
+
* @param { number[] } value
|
|
192
|
+
*/
|
|
193
|
+
set signatureData(value) { this.#digitalseal.signatureData = value; }
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The header zone of the VDS as defined by ICAO 9303 part 13.
|
|
197
|
+
* @type { number[] }
|
|
198
|
+
*/
|
|
199
|
+
get headerZone() {
|
|
200
|
+
let output = [];
|
|
201
|
+
output.push(VDS_MAGIC);
|
|
202
|
+
output.push(VDS_VERSION_3);
|
|
203
|
+
output =
|
|
204
|
+
output.concat(c40Encode(this.authorityCode.padEnd(3, "<")));
|
|
205
|
+
output = output.concat(c40Encode(
|
|
206
|
+
this.identifierCode +
|
|
207
|
+
this.certReference
|
|
208
|
+
));
|
|
209
|
+
output = output.concat(this.issueDate.toVDSArray());
|
|
210
|
+
output = output.concat(this.signatureDate.toVDSArray());
|
|
211
|
+
output.push(this.featureDefinition);
|
|
212
|
+
output.push(this.typeCategory);
|
|
213
|
+
return output;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* @param { number[] } value
|
|
217
|
+
*/
|
|
218
|
+
set headerZone(value) {
|
|
219
|
+
this.#setHeader(0, value);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Given a point 'start' in an array 'value', extract the header data.
|
|
223
|
+
* @param { number } start
|
|
224
|
+
* @param { number[] } value
|
|
225
|
+
* @returns { number } The point in the array where the method stopped
|
|
226
|
+
* reading.
|
|
227
|
+
*/
|
|
228
|
+
#setHeader(start, value) {
|
|
229
|
+
if (value[0] !== VDS_MAGIC) {
|
|
230
|
+
throw new TypeError(
|
|
231
|
+
`Value '${value[0].toString(16).padStart(2, "0").toUpperCase()}' is ` +
|
|
232
|
+
`not an ICAO Digital Seal (` +
|
|
233
|
+
`${VDS_MAGIC.toString(16).padStart(2, "0").toUpperCase()}).`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
if (value[1] !== VDS_VERSION_3) {
|
|
237
|
+
throw new TypeError(
|
|
238
|
+
`Value '${value[0].toString(16).padStart(2, "0").toUpperCase()}' is ` +
|
|
239
|
+
`not version 3 of an ICAO Digital Seal (` +
|
|
240
|
+
`${VDS_VERSION_3.toString(16).padStart(
|
|
241
|
+
2, "0"
|
|
242
|
+
).toUpperCase()}).`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
start += 2;
|
|
246
|
+
this.authority =
|
|
247
|
+
c40Decode(value.slice(start, start + 2)).trim();
|
|
248
|
+
start += 2;
|
|
249
|
+
const ID_CERT_REF = c40Decode(value.slice(start, start + 6));
|
|
250
|
+
this.identifierCode = ID_CERT_REF.substring(0, 4);
|
|
251
|
+
this.certReference = ID_CERT_REF.substring(4);
|
|
252
|
+
start += 6;
|
|
253
|
+
this.issueDate = new IcaoDate(value.slice(start, start + 3));
|
|
254
|
+
start += 3;
|
|
255
|
+
this.signatureDate = new IcaoDate(value.slice(start, start + 3));
|
|
256
|
+
start += 3;
|
|
257
|
+
this.featureDefinition = value[start];
|
|
258
|
+
start += 1;
|
|
259
|
+
this.typeCategory = value[start];
|
|
260
|
+
start += 1;
|
|
261
|
+
return start;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* The message zone of the VDS as defined by ICAO 9303 part 13.
|
|
266
|
+
* @type { number[] }
|
|
267
|
+
*/
|
|
268
|
+
get messageZone() {
|
|
269
|
+
let output = [];
|
|
270
|
+
for (const [tag, value] of this.features) {
|
|
271
|
+
output.push(tag);
|
|
272
|
+
output.push(value.length);
|
|
273
|
+
output = output.concat(value);
|
|
274
|
+
}
|
|
275
|
+
return output;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* @param { number[] } value
|
|
279
|
+
*/
|
|
280
|
+
set messageZone(value) {
|
|
281
|
+
this.#setMessage(0, value);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Given a point 'start' in an array 'value', extract the document feature
|
|
285
|
+
* data.
|
|
286
|
+
* @param { number } start
|
|
287
|
+
* @param { number[] } value
|
|
288
|
+
* @returns { number } The point in the array where the method stopped
|
|
289
|
+
* reading.
|
|
290
|
+
*/
|
|
291
|
+
#setMessage(start, value) {
|
|
292
|
+
this.features.clear();
|
|
293
|
+
while (start < value.length) {
|
|
294
|
+
if (value[start] === VDS_SIGNATURE_MARKER) {
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
const TAG = value[start];
|
|
298
|
+
start += 1;
|
|
299
|
+
const LENGTH = value[start];
|
|
300
|
+
start += 1;
|
|
301
|
+
const slicedValue = value.slice(start, start + LENGTH);
|
|
302
|
+
if (slicedValue.length !== LENGTH) {
|
|
303
|
+
throw new RangeError(
|
|
304
|
+
`Length '${LENGTH}' of document feature does not match the actual ` +
|
|
305
|
+
`length (${slicedValue.length}).`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
this.features.set(TAG, slicedValue);
|
|
309
|
+
start += LENGTH;
|
|
310
|
+
}
|
|
311
|
+
return start;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* The signature zone of the VDS as a TLV of the signature marker, its length
|
|
316
|
+
* in BER/DER definite length form, and the raw signature data.
|
|
317
|
+
* @type { number[] }
|
|
318
|
+
*/
|
|
319
|
+
get signatureZone() { return this.#digitalseal.signatureZone; }
|
|
320
|
+
/**
|
|
321
|
+
* @param { number[] } value
|
|
322
|
+
*/
|
|
323
|
+
set signatureZone(value) { this.#digitalseal.signatureZone = value; }
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* A concatenation of the header zone and the message zone of the VDS.
|
|
327
|
+
* @type { number[] }
|
|
328
|
+
*/
|
|
329
|
+
get unsignedSeal() {
|
|
330
|
+
return this.headerZone.concat(this.messageZone);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* @param { number[] } value
|
|
334
|
+
*/
|
|
335
|
+
set unsignedSeal(value) {
|
|
336
|
+
let start = 0;
|
|
337
|
+
start = this.#setHeader(start, value);
|
|
338
|
+
this.#setMessage(start, value);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* A concatenation of the header zone, the message zone, and the signature
|
|
343
|
+
* zone of the VDS.
|
|
344
|
+
* @type { number[] }
|
|
345
|
+
*/
|
|
346
|
+
get signedSeal() {
|
|
347
|
+
return this.headerZone.concat(this.messageZone.concat(this.signatureZone));
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* @param { number[] } value
|
|
351
|
+
*/
|
|
352
|
+
set signedSeal(value) {
|
|
353
|
+
let start = 0;
|
|
354
|
+
start = this.#setHeader(start, value);
|
|
355
|
+
start = this.#setMessage(start, value);
|
|
356
|
+
this.signatureData = setSignatureZone(start, value);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|