@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.
Files changed (62) hide show
  1. package/LICENSES/CC0-1.0.txt +121 -0
  2. package/LICENSES/LGPL-3.0-or-later.txt +304 -0
  3. package/README.md +79 -0
  4. package/package.json +17 -0
  5. package/package.json.license +3 -0
  6. package/src/digitalseal.js +234 -0
  7. package/src/digitalsealv3.js +359 -0
  8. package/src/digitalsealv4.js +369 -0
  9. package/src/index.js +41 -0
  10. package/src/mrvadocument.js +465 -0
  11. package/src/mrvadocumentwithvds3.js +559 -0
  12. package/src/mrvadocumentwithvds4.js +559 -0
  13. package/src/mrvbdocument.js +466 -0
  14. package/src/mrvbdocumentwithvds3.js +560 -0
  15. package/src/mrvbdocumentwithvds4.js +560 -0
  16. package/src/td1document.js +408 -0
  17. package/src/td1documentwithvds3.js +479 -0
  18. package/src/td1documentwithvds4.js +479 -0
  19. package/src/td2document.js +379 -0
  20. package/src/td2documentwithvds3.js +466 -0
  21. package/src/td2documentwithvds4.js +466 -0
  22. package/src/td3document.js +391 -0
  23. package/src/td3documentwithvds3.js +466 -0
  24. package/src/td3documentwithvds4.js +466 -0
  25. package/src/traveldocument.js +306 -0
  26. package/src/utilities/bytes-to-date.js +23 -0
  27. package/src/utilities/c40-decode.js +225 -0
  28. package/src/utilities/c40-encode.js +254 -0
  29. package/src/utilities/c40-shift1.js +10 -0
  30. package/src/utilities/date-to-bytes.js +38 -0
  31. package/src/utilities/der-length-to-length.js +33 -0
  32. package/src/utilities/expand-year.js +26 -0
  33. package/src/utilities/gender-marker-to-mrz.js +14 -0
  34. package/src/utilities/generate-mrz-check-digit.js +139 -0
  35. package/src/utilities/icao-date.js +234 -0
  36. package/src/utilities/length-to-der-length.js +41 -0
  37. package/src/utilities/nationality-codes.js +303 -0
  38. package/src/utilities/normalize-mrz-string.js +19 -0
  39. package/src/utilities/optional-data-mrz.js +29 -0
  40. package/src/utilities/pad-mrz-string.js +16 -0
  41. package/src/utilities/set-signature-zone.js +34 -0
  42. package/src/utilities/stringify-validation-array.js +27 -0
  43. package/src/utilities/validate-hex-string.js +41 -0
  44. package/src/utilities/validate-identifier-code.js +38 -0
  45. package/src/utilities/validate-mrz-string.js +44 -0
  46. package/src/utilities/vds-magic.js +9 -0
  47. package/src/utilities/vds-signature-marker.js +9 -0
  48. package/src/utilities/vds-version-3.js +9 -0
  49. package/src/utilities/vds-version-4.js +9 -0
  50. package/src/visadocument.js +114 -0
  51. package/tests/models/digitalsealv4.test.js +67 -0
  52. package/tests/models/mrvadocument.test.js +61 -0
  53. package/tests/models/mrvbdocument.test.js +61 -0
  54. package/tests/models/td1document.test.js +50 -0
  55. package/tests/models/td2document.test.js +49 -0
  56. package/tests/models/td3document.test.js +88 -0
  57. package/tests/utilities/c40-encodation-scheme.test.js +34 -0
  58. package/tests/utilities/der-tlv-length-conversion.test.js +99 -0
  59. package/tests/utilities/document-utilities.test.js +199 -0
  60. package/tests/utilities/icao-constants.test.js +35 -0
  61. package/tests/utilities/icao-date.test.js +141 -0
  62. 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
+