dosipas-ts 1.0.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/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/decoder.d.ts +16 -0
- package/dist/decoder.d.ts.map +1 -0
- package/dist/decoder.js +265 -0
- package/dist/decoder.js.map +1 -0
- package/dist/encoder.d.ts +16 -0
- package/dist/encoder.d.ts.map +1 -0
- package/dist/encoder.js +171 -0
- package/dist/encoder.js.map +1 -0
- package/dist/fixtures.d.ts +14 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +68 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/oids.d.ts +28 -0
- package/dist/oids.d.ts.map +1 -0
- package/dist/oids.js +43 -0
- package/dist/oids.js.map +1 -0
- package/dist/schemas.d.ts +11 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +17 -0
- package/dist/schemas.js.map +1 -0
- package/dist/signature-fixtures.d.ts +110 -0
- package/dist/signature-fixtures.d.ts.map +1 -0
- package/dist/signature-fixtures.js +142 -0
- package/dist/signature-fixtures.js.map +1 -0
- package/dist/signature-utils.d.ts +29 -0
- package/dist/signature-utils.d.ts.map +1 -0
- package/dist/signature-utils.js +201 -0
- package/dist/signature-utils.js.map +1 -0
- package/dist/signed-data.d.ts +32 -0
- package/dist/signed-data.d.ts.map +1 -0
- package/dist/signed-data.js +87 -0
- package/dist/signed-data.js.map +1 -0
- package/dist/types.d.ts +273 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/verifier.d.ts +67 -0
- package/dist/verifier.d.ts.map +1 -0
- package/dist/verifier.js +309 -0
- package/dist/verifier.js.map +1 -0
- package/package.json +46 -0
- package/schemas/uic-barcode/intercode6.schema.json +204 -0
- package/schemas/uic-barcode/uicBarcodeHeader_v1.schema.json +376 -0
- package/schemas/uic-barcode/uicBarcodeHeader_v2.schema.json +484 -0
- package/schemas/uic-barcode/uicRailTicketData_v1.schema.json +31507 -0
- package/schemas/uic-barcode/uicRailTicketData_v2.schema.json +31623 -0
- package/schemas/uic-barcode/uicRailTicketData_v3.schema.json +32729 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript types for a decoded UIC barcode ticket with Intercode 6 extensions.
|
|
3
|
+
*
|
|
4
|
+
* The top-level type is {@link UicBarcodeTicket} which combines the header envelope,
|
|
5
|
+
* decoded FCB rail ticket data, and Intercode 6 extension data into a single typed object.
|
|
6
|
+
*/
|
|
7
|
+
/** Fully decoded UIC barcode ticket with resolved Intercode 6 extensions. */
|
|
8
|
+
export interface UicBarcodeTicket {
|
|
9
|
+
/** Header format string, e.g. "U1" or "U2". */
|
|
10
|
+
format: string;
|
|
11
|
+
/** Header version number (1 or 2). */
|
|
12
|
+
headerVersion: number;
|
|
13
|
+
/** Level 2 digital signature bytes, if present. */
|
|
14
|
+
level2Signature?: Uint8Array;
|
|
15
|
+
/** Security / key metadata from Level 1. */
|
|
16
|
+
security: SecurityInfo;
|
|
17
|
+
/** Decoded FCB rail ticket data blocks. */
|
|
18
|
+
railTickets: RailTicketData[];
|
|
19
|
+
/** Raw data blocks that were not identified as FCB. */
|
|
20
|
+
otherDataBlocks: DataBlock[];
|
|
21
|
+
/** Decoded Intercode 6 dynamic data from Level 2, if present. */
|
|
22
|
+
dynamicData?: IntercodeDynamicData;
|
|
23
|
+
/** Raw Level 2 data block, if present. */
|
|
24
|
+
level2DataBlock?: DataBlock;
|
|
25
|
+
}
|
|
26
|
+
export interface SecurityInfo {
|
|
27
|
+
securityProviderNum?: number;
|
|
28
|
+
securityProviderIA5?: string;
|
|
29
|
+
keyId?: number;
|
|
30
|
+
level1KeyAlg?: string;
|
|
31
|
+
level2KeyAlg?: string;
|
|
32
|
+
level1SigningAlg?: string;
|
|
33
|
+
level2SigningAlg?: string;
|
|
34
|
+
level2PublicKey?: Uint8Array;
|
|
35
|
+
level1Signature?: Uint8Array;
|
|
36
|
+
endOfValidityYear?: number;
|
|
37
|
+
endOfValidityDay?: number;
|
|
38
|
+
endOfValidityTime?: number;
|
|
39
|
+
validityDuration?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface DataBlock {
|
|
42
|
+
dataFormat: string;
|
|
43
|
+
data: Uint8Array;
|
|
44
|
+
}
|
|
45
|
+
export interface RailTicketData {
|
|
46
|
+
/** FCB version, e.g. 1, 2, or 3. */
|
|
47
|
+
fcbVersion: number;
|
|
48
|
+
/** Issuing details. */
|
|
49
|
+
issuingDetail?: IssuingDetail;
|
|
50
|
+
/** Traveler information. */
|
|
51
|
+
travelerDetail?: TravelerDetail;
|
|
52
|
+
/** Transport document entries. */
|
|
53
|
+
transportDocument?: TransportDocumentEntry[];
|
|
54
|
+
/** Control detail. */
|
|
55
|
+
controlDetail?: ControlDetail;
|
|
56
|
+
/** The raw decoded object (untyped) for fields not covered above. */
|
|
57
|
+
raw: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
export interface IssuingDetail {
|
|
60
|
+
securityProviderNum?: number;
|
|
61
|
+
securityProviderIA5?: string;
|
|
62
|
+
issuerNum?: number;
|
|
63
|
+
issuerIA5?: string;
|
|
64
|
+
issuingYear: number;
|
|
65
|
+
issuingDay: number;
|
|
66
|
+
issuingTime?: number;
|
|
67
|
+
issuerName?: string;
|
|
68
|
+
specimen: boolean;
|
|
69
|
+
securePaperTicket: boolean;
|
|
70
|
+
activated: boolean;
|
|
71
|
+
currency?: string;
|
|
72
|
+
currencyFract?: number;
|
|
73
|
+
issuerPNR?: string;
|
|
74
|
+
/** Decoded Intercode 6 issuing extension, if present. */
|
|
75
|
+
intercodeIssuing?: IntercodeIssuingData;
|
|
76
|
+
/** Raw extension data if present but not an Intercode extension. */
|
|
77
|
+
extension?: ExtensionData;
|
|
78
|
+
}
|
|
79
|
+
export interface ExtensionData {
|
|
80
|
+
extensionId: string;
|
|
81
|
+
extensionData: Uint8Array;
|
|
82
|
+
}
|
|
83
|
+
export type RetailChannel = 'smsTicket' | 'mobileApplication' | 'webSite' | 'ticketOffice' | 'depositaryTerminal' | 'onBoardTerminal' | 'ticketVendingMachine';
|
|
84
|
+
export interface ProductRetailerData {
|
|
85
|
+
retailChannel?: RetailChannel;
|
|
86
|
+
retailGeneratorId?: number;
|
|
87
|
+
retailServerId?: number;
|
|
88
|
+
retailerId?: number;
|
|
89
|
+
retailPointId?: number;
|
|
90
|
+
}
|
|
91
|
+
export interface IntercodeIssuingData {
|
|
92
|
+
intercodeVersion: number;
|
|
93
|
+
intercodeInstanciation: number;
|
|
94
|
+
networkId: Uint8Array;
|
|
95
|
+
productRetailer?: ProductRetailerData;
|
|
96
|
+
}
|
|
97
|
+
export interface IntercodeDynamicData {
|
|
98
|
+
dynamicContentDay: number;
|
|
99
|
+
dynamicContentTime?: number;
|
|
100
|
+
dynamicContentUTCOffset?: number;
|
|
101
|
+
dynamicContentDuration?: number;
|
|
102
|
+
}
|
|
103
|
+
export interface TravelerDetail {
|
|
104
|
+
traveler?: TravelerInfo[];
|
|
105
|
+
preferredLanguage?: string;
|
|
106
|
+
groupName?: string;
|
|
107
|
+
}
|
|
108
|
+
export interface TravelerInfo {
|
|
109
|
+
firstName?: string;
|
|
110
|
+
secondName?: string;
|
|
111
|
+
lastName?: string;
|
|
112
|
+
idCard?: string;
|
|
113
|
+
passportId?: string;
|
|
114
|
+
title?: string;
|
|
115
|
+
gender?: string;
|
|
116
|
+
customerIdIA5?: string;
|
|
117
|
+
customerIdNum?: number;
|
|
118
|
+
yearOfBirth?: number;
|
|
119
|
+
monthOfBirth?: number;
|
|
120
|
+
dayOfBirth?: number;
|
|
121
|
+
ticketHolder?: boolean;
|
|
122
|
+
passengerType?: string;
|
|
123
|
+
passengerWithReducedMobility?: boolean;
|
|
124
|
+
countryOfResidence?: number;
|
|
125
|
+
countryOfPassport?: number;
|
|
126
|
+
dateOfBirth?: string;
|
|
127
|
+
status?: CustomerStatus[];
|
|
128
|
+
}
|
|
129
|
+
export interface CustomerStatus {
|
|
130
|
+
statusProviderNum?: number;
|
|
131
|
+
statusProviderIA5?: string;
|
|
132
|
+
customerStatus?: number;
|
|
133
|
+
customerStatusDescr?: string;
|
|
134
|
+
}
|
|
135
|
+
export interface TransportDocumentEntry {
|
|
136
|
+
/** The variant name of the ticket CHOICE, e.g. "openTicket", "reservation", etc. */
|
|
137
|
+
ticketType: string;
|
|
138
|
+
/** Decoded ticket data (type depends on ticketType). */
|
|
139
|
+
ticket: Record<string, unknown>;
|
|
140
|
+
}
|
|
141
|
+
export interface ControlDetail {
|
|
142
|
+
identificationByCardReference?: CardReference[];
|
|
143
|
+
identificationByIdCard?: boolean;
|
|
144
|
+
identificationByPassportId?: boolean;
|
|
145
|
+
passportValidationRequired?: boolean;
|
|
146
|
+
onlineValidationRequired?: boolean;
|
|
147
|
+
ageCheckRequired?: boolean;
|
|
148
|
+
reductionCardCheckRequired?: boolean;
|
|
149
|
+
infoText?: string;
|
|
150
|
+
includedTickets?: TicketLink[];
|
|
151
|
+
extension?: ExtensionData;
|
|
152
|
+
}
|
|
153
|
+
export interface CardReference {
|
|
154
|
+
trailingCardIdNum?: number;
|
|
155
|
+
trailingCardIdIA5?: string;
|
|
156
|
+
cardName?: string;
|
|
157
|
+
cardIdNum?: number;
|
|
158
|
+
cardIdIA5?: string;
|
|
159
|
+
leadingCardIdNum?: number;
|
|
160
|
+
leadingCardIdIA5?: string;
|
|
161
|
+
cardTypeNum?: number;
|
|
162
|
+
cardTypeDescr?: string;
|
|
163
|
+
}
|
|
164
|
+
export interface TicketLink {
|
|
165
|
+
referenceIA5?: string;
|
|
166
|
+
referenceNum?: number;
|
|
167
|
+
issuerName?: string;
|
|
168
|
+
issuerPNR?: string;
|
|
169
|
+
productOwnerNum?: number;
|
|
170
|
+
productOwnerIA5?: string;
|
|
171
|
+
ticketType?: string;
|
|
172
|
+
linkMode?: string;
|
|
173
|
+
}
|
|
174
|
+
/** Input for encoding a UIC barcode ticket. */
|
|
175
|
+
export interface UicBarcodeTicketInput {
|
|
176
|
+
/** Header version (1 or 2). Default: 2. */
|
|
177
|
+
headerVersion?: number;
|
|
178
|
+
/** RICS code of the security provider. */
|
|
179
|
+
securityProviderNum?: number;
|
|
180
|
+
/** Key ID for signature verification. */
|
|
181
|
+
keyId?: number;
|
|
182
|
+
/** Level 1 key algorithm OID. */
|
|
183
|
+
level1KeyAlg?: string;
|
|
184
|
+
/** Level 2 key algorithm OID. */
|
|
185
|
+
level2KeyAlg?: string;
|
|
186
|
+
/** Level 1 signing algorithm OID. */
|
|
187
|
+
level1SigningAlg?: string;
|
|
188
|
+
/** Level 2 signing algorithm OID. */
|
|
189
|
+
level2SigningAlg?: string;
|
|
190
|
+
/** Level 2 public key bytes. */
|
|
191
|
+
level2PublicKey?: Uint8Array;
|
|
192
|
+
/** Level 1 signature bytes (placeholder). */
|
|
193
|
+
level1Signature?: Uint8Array;
|
|
194
|
+
/** Level 2 signature bytes (placeholder). */
|
|
195
|
+
level2Signature?: Uint8Array;
|
|
196
|
+
/** FCB version (1, 2 or 3). Default: 2. */
|
|
197
|
+
fcbVersion?: number;
|
|
198
|
+
/** The rail ticket data to encode. */
|
|
199
|
+
railTicket: RailTicketInput;
|
|
200
|
+
/** Intercode 6 dynamic data for Level 2. */
|
|
201
|
+
dynamicData?: IntercodeDynamicDataInput;
|
|
202
|
+
}
|
|
203
|
+
export interface RailTicketInput {
|
|
204
|
+
issuingDetail: IssuingDetailInput;
|
|
205
|
+
travelerDetail?: TravelerDetailInput;
|
|
206
|
+
transportDocument?: TransportDocumentInput[];
|
|
207
|
+
controlDetail?: Record<string, unknown>;
|
|
208
|
+
}
|
|
209
|
+
export interface IssuingDetailInput {
|
|
210
|
+
securityProviderNum?: number;
|
|
211
|
+
issuerNum?: number;
|
|
212
|
+
issuingYear: number;
|
|
213
|
+
issuingDay: number;
|
|
214
|
+
issuingTime?: number;
|
|
215
|
+
issuerName?: string;
|
|
216
|
+
specimen?: boolean;
|
|
217
|
+
securePaperTicket?: boolean;
|
|
218
|
+
activated?: boolean;
|
|
219
|
+
currency?: string;
|
|
220
|
+
currencyFract?: number;
|
|
221
|
+
issuerPNR?: string;
|
|
222
|
+
/** Intercode 6 issuing extension data. */
|
|
223
|
+
intercodeIssuing?: IntercodeIssuingDataInput;
|
|
224
|
+
}
|
|
225
|
+
export interface IntercodeIssuingDataInput {
|
|
226
|
+
intercodeVersion?: number;
|
|
227
|
+
intercodeInstanciation?: number;
|
|
228
|
+
networkId: Uint8Array;
|
|
229
|
+
productRetailer?: ProductRetailerData;
|
|
230
|
+
}
|
|
231
|
+
export interface IntercodeDynamicDataInput {
|
|
232
|
+
/** RICS code for the dataFormat field (e.g. 3703 → "_3703.ID1"). */
|
|
233
|
+
rics: number;
|
|
234
|
+
dynamicContentDay?: number;
|
|
235
|
+
dynamicContentTime?: number;
|
|
236
|
+
dynamicContentUTCOffset?: number;
|
|
237
|
+
dynamicContentDuration?: number;
|
|
238
|
+
}
|
|
239
|
+
export interface TravelerDetailInput {
|
|
240
|
+
traveler?: Partial<TravelerInfo>[];
|
|
241
|
+
preferredLanguage?: string;
|
|
242
|
+
groupName?: string;
|
|
243
|
+
}
|
|
244
|
+
export interface TransportDocumentInput {
|
|
245
|
+
ticketType: string;
|
|
246
|
+
ticket: Record<string, unknown>;
|
|
247
|
+
}
|
|
248
|
+
/** Result of a signature verification attempt for a single level. */
|
|
249
|
+
export interface SignatureLevelResult {
|
|
250
|
+
valid: boolean;
|
|
251
|
+
error?: string;
|
|
252
|
+
algorithm?: string;
|
|
253
|
+
}
|
|
254
|
+
/** Result of signature verification for both levels. */
|
|
255
|
+
export interface SignatureVerificationResult {
|
|
256
|
+
level1: SignatureLevelResult;
|
|
257
|
+
level2: SignatureLevelResult;
|
|
258
|
+
}
|
|
259
|
+
/** Options for signature verification. */
|
|
260
|
+
export interface VerifyOptions {
|
|
261
|
+
/** Provider for Level 1 public keys (looked up by issuer + keyId). */
|
|
262
|
+
level1KeyProvider?: Level1KeyProvider;
|
|
263
|
+
/** Explicit Level 1 public key bytes (alternative to level1KeyProvider). */
|
|
264
|
+
level1PublicKey?: Uint8Array;
|
|
265
|
+
}
|
|
266
|
+
/** Provider interface for looking up Level 1 public keys. */
|
|
267
|
+
export interface Level1KeyProvider {
|
|
268
|
+
getPublicKey(securityProvider: {
|
|
269
|
+
num?: number;
|
|
270
|
+
ia5?: string;
|
|
271
|
+
}, keyId: number, keyAlg?: string): Promise<Uint8Array>;
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript types for a decoded UIC barcode ticket with Intercode 6 extensions.
|
|
3
|
+
*
|
|
4
|
+
* The top-level type is {@link UicBarcodeTicket} which combines the header envelope,
|
|
5
|
+
* decoded FCB rail ticket data, and Intercode 6 extension data into a single typed object.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { SignatureVerificationResult, VerifyOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Verify Level 2 signature on a UIC barcode.
|
|
4
|
+
*
|
|
5
|
+
* Level 2 is self-contained: the public key is embedded in the barcode's
|
|
6
|
+
* `level1Data.level2PublicKey` field.
|
|
7
|
+
*
|
|
8
|
+
* @param bytes - Raw barcode payload bytes.
|
|
9
|
+
* @returns Verification result with valid flag and optional error.
|
|
10
|
+
*/
|
|
11
|
+
export declare function verifyLevel2Signature(bytes: Uint8Array): Promise<{
|
|
12
|
+
valid: boolean;
|
|
13
|
+
error?: string;
|
|
14
|
+
algorithm?: string;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Verify Level 1 signature on a UIC barcode.
|
|
18
|
+
*
|
|
19
|
+
* Level 1 requires an externally-provided public key since it is not
|
|
20
|
+
* embedded in the barcode.
|
|
21
|
+
*
|
|
22
|
+
* @param bytes - Raw barcode payload bytes.
|
|
23
|
+
* @param publicKey - The Level 1 public key bytes.
|
|
24
|
+
* @returns Verification result with valid flag and optional error.
|
|
25
|
+
*/
|
|
26
|
+
export declare function verifyLevel1Signature(bytes: Uint8Array, publicKey: Uint8Array): Promise<{
|
|
27
|
+
valid: boolean;
|
|
28
|
+
error?: string;
|
|
29
|
+
algorithm?: string;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Verify both Level 1 and Level 2 signatures on a UIC barcode.
|
|
33
|
+
*
|
|
34
|
+
* @param bytes - Raw barcode payload bytes.
|
|
35
|
+
* @param options - Verification options (key provider or explicit key).
|
|
36
|
+
* @returns Combined verification results for both levels.
|
|
37
|
+
*/
|
|
38
|
+
export declare function verifySignatures(bytes: Uint8Array, options?: VerifyOptions): Promise<SignatureVerificationResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Parse the UIC public key XML and find a key by issuer code and key ID.
|
|
41
|
+
*
|
|
42
|
+
* @param xml - XML string from https://railpublickey.uic.org/download.php
|
|
43
|
+
* @param issuerCode - The issuer RICS code (securityProviderNum)
|
|
44
|
+
* @param keyId - The key identifier
|
|
45
|
+
* @returns The Base64-decoded public key bytes, or null if not found.
|
|
46
|
+
*/
|
|
47
|
+
export declare function findKeyInXml(xml: string, issuerCode: number, keyId: number): Uint8Array | null;
|
|
48
|
+
/**
|
|
49
|
+
* Parse all keys from the UIC public key XML.
|
|
50
|
+
*
|
|
51
|
+
* @param xml - XML string from https://railpublickey.uic.org/download.php
|
|
52
|
+
* @returns Array of parsed key entries.
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseKeysXml(xml: string): UicPublicKeyEntry[];
|
|
55
|
+
export interface UicPublicKeyEntry {
|
|
56
|
+
issuerCode: number;
|
|
57
|
+
id: number;
|
|
58
|
+
issuerName: string;
|
|
59
|
+
publicKey: Uint8Array;
|
|
60
|
+
publicKeyB64: string;
|
|
61
|
+
signatureAlgorithm: string;
|
|
62
|
+
versionType: string;
|
|
63
|
+
barcodeVersion: string;
|
|
64
|
+
startDate: string;
|
|
65
|
+
endDate: string;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=verifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../src/verifier.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,2BAA2B,EAE3B,aAAa,EACd,MAAM,SAAS,CAAC;AAgEjB;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8DjE;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAoEjE;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,2BAA2B,CAAC,CAiCtC;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAyB9F;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAmC7D;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,UAAU,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB"}
|
package/dist/verifier.js
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signature verification for UIC barcode tickets.
|
|
3
|
+
*
|
|
4
|
+
* Supports two-level signature verification:
|
|
5
|
+
* - Level 2: self-contained, uses the embedded `level2PublicKey`
|
|
6
|
+
* - Level 1: requires an external public key (via provider or direct)
|
|
7
|
+
*
|
|
8
|
+
* Uses @noble/curves for ECDSA verification (works in both Node.js and browsers).
|
|
9
|
+
*/
|
|
10
|
+
import { p256, p384, p521 } from '@noble/curves/nist.js';
|
|
11
|
+
import { extractSignedData } from './signed-data';
|
|
12
|
+
import { getSigningAlgorithm, getKeyAlgorithm } from './oids';
|
|
13
|
+
import { derToRaw, extractEcPublicKeyPoint } from './signature-utils';
|
|
14
|
+
// UIC barcode signatures may have non-normalized (high-S) values, so we
|
|
15
|
+
// disable the lowS check that @noble/curves enforces by default.
|
|
16
|
+
const VERIFY_OPTS = { lowS: false };
|
|
17
|
+
function getCurveOps(curve) {
|
|
18
|
+
switch (curve) {
|
|
19
|
+
case 'P-256':
|
|
20
|
+
return {
|
|
21
|
+
verify: (sig, msg, pk) => p256.verify(sig, msg, pk, VERIFY_OPTS),
|
|
22
|
+
componentLength: 32,
|
|
23
|
+
};
|
|
24
|
+
case 'P-384':
|
|
25
|
+
return {
|
|
26
|
+
verify: (sig, msg, pk) => p384.verify(sig, msg, pk, VERIFY_OPTS),
|
|
27
|
+
componentLength: 48,
|
|
28
|
+
};
|
|
29
|
+
case 'P-521':
|
|
30
|
+
return {
|
|
31
|
+
verify: (sig, msg, pk) => p521.verify(sig, msg, pk, VERIFY_OPTS),
|
|
32
|
+
componentLength: 66,
|
|
33
|
+
};
|
|
34
|
+
default:
|
|
35
|
+
throw new Error(`Unsupported curve: ${curve}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// ECDSA verification
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
function verifyEcdsa(signatureBytes, signedData, publicKeyBytes, curve) {
|
|
42
|
+
const curveOps = getCurveOps(curve);
|
|
43
|
+
// Convert DER signature to raw (r || s) compact format
|
|
44
|
+
const rawSig = derToRaw(signatureBytes, curveOps.componentLength);
|
|
45
|
+
// Extract the raw EC point from potentially SPKI-wrapped key
|
|
46
|
+
const rawPoint = extractEcPublicKeyPoint(publicKeyBytes);
|
|
47
|
+
// Verify — @noble/curves hashes the message internally (prehash: true by default)
|
|
48
|
+
return curveOps.verify(rawSig, signedData, rawPoint);
|
|
49
|
+
}
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Public API
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
/**
|
|
54
|
+
* Verify Level 2 signature on a UIC barcode.
|
|
55
|
+
*
|
|
56
|
+
* Level 2 is self-contained: the public key is embedded in the barcode's
|
|
57
|
+
* `level1Data.level2PublicKey` field.
|
|
58
|
+
*
|
|
59
|
+
* @param bytes - Raw barcode payload bytes.
|
|
60
|
+
* @returns Verification result with valid flag and optional error.
|
|
61
|
+
*/
|
|
62
|
+
export async function verifyLevel2Signature(bytes) {
|
|
63
|
+
try {
|
|
64
|
+
const extracted = extractSignedData(bytes);
|
|
65
|
+
const { security } = extracted;
|
|
66
|
+
if (!security.level2Signature) {
|
|
67
|
+
return { valid: false, error: 'Missing level 2 signature' };
|
|
68
|
+
}
|
|
69
|
+
if (!security.level2PublicKey) {
|
|
70
|
+
return { valid: false, error: 'Missing level 2 public key' };
|
|
71
|
+
}
|
|
72
|
+
// Determine algorithms
|
|
73
|
+
const sigAlg = security.level2SigningAlg
|
|
74
|
+
? getSigningAlgorithm(security.level2SigningAlg)
|
|
75
|
+
: undefined;
|
|
76
|
+
const keyAlg = security.level2KeyAlg
|
|
77
|
+
? getKeyAlgorithm(security.level2KeyAlg)
|
|
78
|
+
: undefined;
|
|
79
|
+
if (!sigAlg) {
|
|
80
|
+
return {
|
|
81
|
+
valid: false,
|
|
82
|
+
error: security.level2SigningAlg
|
|
83
|
+
? `Unsupported signing algorithm: ${security.level2SigningAlg}`
|
|
84
|
+
: 'Missing level 2 signing algorithm OID',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (sigAlg.type !== 'ECDSA') {
|
|
88
|
+
return { valid: false, error: `Unsupported signing type for level 2: ${sigAlg.type}` };
|
|
89
|
+
}
|
|
90
|
+
const curve = keyAlg?.curve;
|
|
91
|
+
if (!curve) {
|
|
92
|
+
return {
|
|
93
|
+
valid: false,
|
|
94
|
+
error: security.level2KeyAlg
|
|
95
|
+
? `Cannot determine curve from key algorithm: ${security.level2KeyAlg}`
|
|
96
|
+
: 'Missing level 2 key algorithm OID',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const algorithmDesc = `${sigAlg.type} ${curve} with ${sigAlg.hash}`;
|
|
100
|
+
const valid = verifyEcdsa(security.level2Signature, extracted.level2SignedBytes, security.level2PublicKey, curve);
|
|
101
|
+
return {
|
|
102
|
+
valid,
|
|
103
|
+
algorithm: algorithmDesc,
|
|
104
|
+
...(!valid && { error: `Level 2 signature verification failed (${algorithmDesc})` }),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
return { valid: false, error: e instanceof Error ? e.message : 'Verification failed' };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Verify Level 1 signature on a UIC barcode.
|
|
113
|
+
*
|
|
114
|
+
* Level 1 requires an externally-provided public key since it is not
|
|
115
|
+
* embedded in the barcode.
|
|
116
|
+
*
|
|
117
|
+
* @param bytes - Raw barcode payload bytes.
|
|
118
|
+
* @param publicKey - The Level 1 public key bytes.
|
|
119
|
+
* @returns Verification result with valid flag and optional error.
|
|
120
|
+
*/
|
|
121
|
+
export async function verifyLevel1Signature(bytes, publicKey) {
|
|
122
|
+
try {
|
|
123
|
+
const extracted = extractSignedData(bytes);
|
|
124
|
+
const { security } = extracted;
|
|
125
|
+
if (!security.level1Signature) {
|
|
126
|
+
return { valid: false, error: 'Missing level 1 signature' };
|
|
127
|
+
}
|
|
128
|
+
// Determine algorithms
|
|
129
|
+
const sigAlg = security.level1SigningAlg
|
|
130
|
+
? getSigningAlgorithm(security.level1SigningAlg)
|
|
131
|
+
: undefined;
|
|
132
|
+
const keyAlg = security.level1KeyAlg
|
|
133
|
+
? getKeyAlgorithm(security.level1KeyAlg)
|
|
134
|
+
: undefined;
|
|
135
|
+
if (!sigAlg) {
|
|
136
|
+
return {
|
|
137
|
+
valid: false,
|
|
138
|
+
error: security.level1SigningAlg
|
|
139
|
+
? `Unsupported signing algorithm: ${security.level1SigningAlg}`
|
|
140
|
+
: 'Missing level 1 signing algorithm OID',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const algorithmDesc = `${sigAlg.type} with ${sigAlg.hash}`;
|
|
144
|
+
if (sigAlg.type === 'ECDSA') {
|
|
145
|
+
const curve = keyAlg?.curve;
|
|
146
|
+
if (!curve) {
|
|
147
|
+
return {
|
|
148
|
+
valid: false,
|
|
149
|
+
error: security.level1KeyAlg
|
|
150
|
+
? `Cannot determine curve from key algorithm: ${security.level1KeyAlg}`
|
|
151
|
+
: 'Missing level 1 key algorithm OID',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const l1AlgorithmDesc = `ECDSA ${curve} with ${sigAlg.hash}`;
|
|
155
|
+
const valid = verifyEcdsa(security.level1Signature, extracted.level1DataBytes, publicKey, curve);
|
|
156
|
+
return {
|
|
157
|
+
valid,
|
|
158
|
+
algorithm: l1AlgorithmDesc,
|
|
159
|
+
...(!valid && { error: `Level 1 signature verification failed (${l1AlgorithmDesc})` }),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (sigAlg.type === 'DSA') {
|
|
163
|
+
// DSA is not supported by @noble/curves
|
|
164
|
+
// DSA signatures use the same DER format but different crypto primitives
|
|
165
|
+
return {
|
|
166
|
+
valid: false,
|
|
167
|
+
error: `DSA verification not supported (algorithm: DSA with ${sigAlg.hash})`,
|
|
168
|
+
algorithm: algorithmDesc,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return { valid: false, error: `Unsupported algorithm type: ${sigAlg.type}` };
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
return { valid: false, error: e instanceof Error ? e.message : 'Verification failed' };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Verify both Level 1 and Level 2 signatures on a UIC barcode.
|
|
179
|
+
*
|
|
180
|
+
* @param bytes - Raw barcode payload bytes.
|
|
181
|
+
* @param options - Verification options (key provider or explicit key).
|
|
182
|
+
* @returns Combined verification results for both levels.
|
|
183
|
+
*/
|
|
184
|
+
export async function verifySignatures(bytes, options) {
|
|
185
|
+
// Level 2 verification (self-contained)
|
|
186
|
+
const level2 = await verifyLevel2Signature(bytes);
|
|
187
|
+
// Level 1 verification (needs external key)
|
|
188
|
+
let level1;
|
|
189
|
+
if (options?.level1PublicKey) {
|
|
190
|
+
level1 = await verifyLevel1Signature(bytes, options.level1PublicKey);
|
|
191
|
+
}
|
|
192
|
+
else if (options?.level1KeyProvider) {
|
|
193
|
+
try {
|
|
194
|
+
const extracted = extractSignedData(bytes);
|
|
195
|
+
const { security } = extracted;
|
|
196
|
+
const pubKey = await options.level1KeyProvider.getPublicKey({ num: security.securityProviderNum, ia5: security.securityProviderIA5 }, security.keyId ?? 0, security.level1KeyAlg);
|
|
197
|
+
level1 = await verifyLevel1Signature(bytes, pubKey);
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
level1 = {
|
|
201
|
+
valid: false,
|
|
202
|
+
error: `Key provider error: ${e instanceof Error ? e.message : 'unknown error'}`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
level1 = {
|
|
208
|
+
valid: false,
|
|
209
|
+
error: 'No level 1 public key provided (use level1PublicKey or level1KeyProvider)',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return { level1, level2 };
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Parse the UIC public key XML and find a key by issuer code and key ID.
|
|
216
|
+
*
|
|
217
|
+
* @param xml - XML string from https://railpublickey.uic.org/download.php
|
|
218
|
+
* @param issuerCode - The issuer RICS code (securityProviderNum)
|
|
219
|
+
* @param keyId - The key identifier
|
|
220
|
+
* @returns The Base64-decoded public key bytes, or null if not found.
|
|
221
|
+
*/
|
|
222
|
+
export function findKeyInXml(xml, issuerCode, keyId) {
|
|
223
|
+
// Simple regex-based XML parser (no DOM dependency for Node.js compatibility)
|
|
224
|
+
const keyRegex = /<key>([\s\S]*?)<\/key>/g;
|
|
225
|
+
let match;
|
|
226
|
+
while ((match = keyRegex.exec(xml)) !== null) {
|
|
227
|
+
const block = match[1];
|
|
228
|
+
const issuerMatch = block.match(/<issuerCode>\s*(\d+)\s*<\/issuerCode>/);
|
|
229
|
+
const idMatch = block.match(/<id>\s*(\d+)\s*<\/id>/);
|
|
230
|
+
const pubKeyMatch = block.match(/<publicKey>\s*([A-Za-z0-9+/=\s]+?)\s*<\/publicKey>/);
|
|
231
|
+
if (issuerMatch && idMatch && pubKeyMatch) {
|
|
232
|
+
const xmlIssuerCode = parseInt(issuerMatch[1], 10);
|
|
233
|
+
const xmlKeyId = parseInt(idMatch[1], 10);
|
|
234
|
+
if (xmlIssuerCode === issuerCode && xmlKeyId === keyId) {
|
|
235
|
+
// Base64 decode
|
|
236
|
+
const b64 = pubKeyMatch[1].replace(/\s+/g, '');
|
|
237
|
+
return base64ToBytes(b64);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Parse all keys from the UIC public key XML.
|
|
245
|
+
*
|
|
246
|
+
* @param xml - XML string from https://railpublickey.uic.org/download.php
|
|
247
|
+
* @returns Array of parsed key entries.
|
|
248
|
+
*/
|
|
249
|
+
export function parseKeysXml(xml) {
|
|
250
|
+
const entries = [];
|
|
251
|
+
const keyRegex = /<key>([\s\S]*?)<\/key>/g;
|
|
252
|
+
let match;
|
|
253
|
+
while ((match = keyRegex.exec(xml)) !== null) {
|
|
254
|
+
const block = match[1];
|
|
255
|
+
const issuerCode = extractXmlInt(block, 'issuerCode');
|
|
256
|
+
const id = extractXmlInt(block, 'id');
|
|
257
|
+
const issuerName = extractXmlText(block, 'issuerName');
|
|
258
|
+
const publicKeyB64 = extractXmlText(block, 'publicKey');
|
|
259
|
+
const signatureAlgorithm = extractXmlText(block, 'signatureAlgorithm');
|
|
260
|
+
const versionType = extractXmlText(block, 'versionType');
|
|
261
|
+
const barcodeVersion = extractXmlText(block, 'barcodeVersion');
|
|
262
|
+
const startDate = extractXmlText(block, 'startDate');
|
|
263
|
+
const endDate = extractXmlText(block, 'endDate');
|
|
264
|
+
if (issuerCode != null && id != null && publicKeyB64) {
|
|
265
|
+
entries.push({
|
|
266
|
+
issuerCode,
|
|
267
|
+
id,
|
|
268
|
+
issuerName: issuerName ?? '',
|
|
269
|
+
publicKey: base64ToBytes(publicKeyB64.replace(/\s+/g, '')),
|
|
270
|
+
publicKeyB64: publicKeyB64.replace(/\s+/g, ''),
|
|
271
|
+
signatureAlgorithm: signatureAlgorithm ?? '',
|
|
272
|
+
versionType: versionType ?? '',
|
|
273
|
+
barcodeVersion: barcodeVersion ?? '',
|
|
274
|
+
startDate: startDate ?? '',
|
|
275
|
+
endDate: endDate ?? '',
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return entries;
|
|
280
|
+
}
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
// Helpers
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
function extractXmlText(block, tag) {
|
|
285
|
+
const re = new RegExp(`<${tag}>\\s*([\\s\\S]*?)\\s*</${tag}>`);
|
|
286
|
+
const m = block.match(re);
|
|
287
|
+
return m ? m[1].trim() : null;
|
|
288
|
+
}
|
|
289
|
+
function extractXmlInt(block, tag) {
|
|
290
|
+
const text = extractXmlText(block, tag);
|
|
291
|
+
if (text === null)
|
|
292
|
+
return null;
|
|
293
|
+
const n = parseInt(text, 10);
|
|
294
|
+
return isNaN(n) ? null : n;
|
|
295
|
+
}
|
|
296
|
+
function base64ToBytes(b64) {
|
|
297
|
+
// Works in both Node.js and browsers
|
|
298
|
+
if (typeof atob === 'function') {
|
|
299
|
+
const binary = atob(b64);
|
|
300
|
+
const bytes = new Uint8Array(binary.length);
|
|
301
|
+
for (let i = 0; i < binary.length; i++) {
|
|
302
|
+
bytes[i] = binary.charCodeAt(i);
|
|
303
|
+
}
|
|
304
|
+
return bytes;
|
|
305
|
+
}
|
|
306
|
+
// Node.js fallback
|
|
307
|
+
return new Uint8Array(Buffer.from(b64, 'base64'));
|
|
308
|
+
}
|
|
309
|
+
//# sourceMappingURL=verifier.js.map
|