@trustchex/react-native-sdk 1.409.0 → 1.464.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/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKModule.kt +2 -8
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +59 -1
- package/ios/Camera/TrustchexCameraView.swift +9 -1
- package/lib/module/Screens/Debug/NFCScanTestScreen.js +635 -0
- package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +1 -4
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +17 -4
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +102 -23
- package/lib/module/Screens/Dynamic/VerbalConsentScreen.js +1079 -0
- package/lib/module/Screens/Dynamic/VideoCallScreen.js +3 -1
- package/lib/module/Screens/Static/ResultScreen.js +128 -22
- package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +8 -0
- package/lib/module/Shared/Animations/recording.json +1 -0
- package/lib/module/Shared/Components/DebugNavigationPanel.js +69 -71
- package/lib/module/Shared/Components/EIDScanner.js +212 -108
- package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +5 -3
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +53 -36
- package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +13 -4
- package/lib/module/Shared/Components/NavigationManager.js +24 -16
- package/lib/module/Shared/EIDReader/aesSecureMessagingWrapper.js +51 -0
- package/lib/module/Shared/EIDReader/apduLevelPACECapable.js +3 -0
- package/lib/module/Shared/EIDReader/bacKey.js +16 -2
- package/lib/module/Shared/EIDReader/eidReader.js +354 -13
- package/lib/module/Shared/EIDReader/eidService.js +25 -1
- package/lib/module/Shared/EIDReader/nfcManagerCardService.js +4 -7
- package/lib/module/Shared/EIDReader/paceInfo.js +85 -0
- package/lib/module/Shared/EIDReader/paceKeySpec.js +51 -0
- package/lib/module/Shared/EIDReader/protocol/paceAPDUSender.js +100 -0
- package/lib/module/Shared/EIDReader/protocol/paceProtocol.js +655 -0
- package/lib/module/Shared/EIDReader/protocol/paceResult.js +37 -0
- package/lib/module/Shared/EIDReader/secureMessagingWrapper.js +27 -4
- package/lib/module/Shared/EIDReader/smartcards/commandAPDU.js +2 -1
- package/lib/module/Shared/EIDReader/tlv/tlv.helpers.js +1 -1
- package/lib/module/Shared/EIDReader/tlv/tlv.utils.js +6 -3
- package/lib/module/Shared/EIDReader/utils/aesCrypto.utils.js +189 -0
- package/lib/module/Shared/Libs/analytics.utils.js +4 -0
- package/lib/module/Shared/Libs/contains.js +1 -40
- package/lib/module/Shared/Libs/country-display.utils.js +34 -0
- package/lib/module/Shared/Libs/demo.utils.js +8 -0
- package/lib/module/Shared/Libs/mrz.utils.js +3 -2
- package/lib/module/Shared/Libs/status-bar.utils.js +4 -2
- package/lib/module/Shared/Types/analytics.types.js +2 -0
- package/lib/module/Translation/Resources/en.js +41 -2
- package/lib/module/Translation/Resources/tr.js +41 -2
- package/lib/module/Trustchex.js +54 -20
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Debug/NFCScanTestScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Debug/NFCScanTestScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/VerbalConsentScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Dynamic/VerbalConsentScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +5 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/aesSecureMessagingWrapper.d.ts +18 -0
- package/lib/typescript/src/Shared/EIDReader/aesSecureMessagingWrapper.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/apduLevelPACECapable.d.ts +23 -0
- package/lib/typescript/src/Shared/EIDReader/apduLevelPACECapable.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/bacKey.d.ts +6 -0
- package/lib/typescript/src/Shared/EIDReader/bacKey.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/eidService.d.ts +9 -0
- package/lib/typescript/src/Shared/EIDReader/eidService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/nfcManagerCardService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/paceInfo.d.ts +50 -0
- package/lib/typescript/src/Shared/EIDReader/paceInfo.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/paceKeySpec.d.ts +30 -0
- package/lib/typescript/src/Shared/EIDReader/paceKeySpec.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceAPDUSender.d.ts +17 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceAPDUSender.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts +105 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceResult.d.ts +24 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceResult.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/secureMessagingWrapper.d.ts +15 -0
- package/lib/typescript/src/Shared/EIDReader/secureMessagingWrapper.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/smartcards/commandAPDU.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/tlv/tlv.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/utils/aesCrypto.utils.d.ts +39 -0
- package/lib/typescript/src/Shared/EIDReader/utils/aesCrypto.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/contains.d.ts +0 -7
- package/lib/typescript/src/Shared/Libs/contains.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts +2 -0
- package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/demo.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts +2 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +10 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +40 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +40 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +7 -4
- package/src/Screens/Debug/NFCScanTestScreen.tsx +692 -0
- package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +1 -4
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +21 -4
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +124 -23
- package/src/Screens/Dynamic/VerbalConsentScreen.tsx +1401 -0
- package/src/Screens/Dynamic/VideoCallScreen.tsx +3 -1
- package/src/Screens/Static/ResultScreen.tsx +183 -31
- package/src/Screens/Static/VerificationSessionCheckScreen.tsx +9 -0
- package/src/Shared/Animations/recording.json +1 -0
- package/src/Shared/Components/DebugNavigationPanel.tsx +73 -48
- package/src/Shared/Components/EIDScanner.tsx +222 -111
- package/src/Shared/Components/IdentityDocumentCamera.flows.ts +7 -4
- package/src/Shared/Components/IdentityDocumentCamera.tsx +199 -184
- package/src/Shared/Components/IdentityDocumentCamera.utils.ts +13 -4
- package/src/Shared/Components/NavigationManager.tsx +27 -18
- package/src/Shared/EIDReader/aesSecureMessagingWrapper.ts +69 -0
- package/src/Shared/EIDReader/apduLevelPACECapable.ts +34 -0
- package/src/Shared/EIDReader/bacKey.ts +24 -8
- package/src/Shared/EIDReader/eidReader.ts +398 -12
- package/src/Shared/EIDReader/eidService.ts +49 -1
- package/src/Shared/EIDReader/nfcManagerCardService.ts +4 -6
- package/src/Shared/EIDReader/paceInfo.ts +159 -0
- package/src/Shared/EIDReader/paceKeySpec.ts +56 -0
- package/src/Shared/EIDReader/protocol/paceAPDUSender.ts +163 -0
- package/src/Shared/EIDReader/protocol/paceProtocol.ts +946 -0
- package/src/Shared/EIDReader/protocol/paceResult.ts +62 -0
- package/src/Shared/EIDReader/secureMessagingWrapper.ts +28 -10
- package/src/Shared/EIDReader/smartcards/commandAPDU.ts +2 -1
- package/src/Shared/EIDReader/tlv/tlv.helpers.ts +1 -1
- package/src/Shared/EIDReader/tlv/tlv.utils.ts +8 -5
- package/src/Shared/EIDReader/utils/aesCrypto.utils.ts +217 -0
- package/src/Shared/Libs/analytics.utils.ts +4 -0
- package/src/Shared/Libs/contains.ts +0 -53
- package/src/Shared/Libs/country-display.utils.ts +55 -0
- package/src/Shared/Libs/crypto.utils.ts +2 -2
- package/src/Shared/Libs/demo.utils.ts +10 -0
- package/src/Shared/Libs/http-client.ts +12 -4
- package/src/Shared/Libs/mrz.utils.ts +3 -2
- package/src/Shared/Libs/status-bar.utils.ts +4 -2
- package/src/Shared/Services/VideoSessionService.ts +1 -1
- package/src/Shared/Types/analytics.types.ts +2 -0
- package/src/Shared/Types/identificationInfo.ts +11 -0
- package/src/Translation/Resources/en.ts +63 -3
- package/src/Translation/Resources/tr.ts +62 -3
- package/src/Trustchex.tsx +53 -17
- package/src/version.ts +1 -1
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { AESSecureMessagingWrapper } from "../aesSecureMessagingWrapper.js";
|
|
4
|
+
import { TripleDesSecureMessagingWrapper } from "../tripleDesSecureMessagingWrapper.js";
|
|
5
|
+
import { PACEInfo } from "../paceInfo.js";
|
|
6
|
+
import { PACEResult } from "./paceResult.js";
|
|
7
|
+
import { PACEKeySpec } from "../paceKeySpec.js";
|
|
8
|
+
import { EID_CONSTANTS } from "../constants/eidConstants.js";
|
|
9
|
+
import { aesDecryptCBC, aesEncryptCBC, aesCMAC, deriveKey } from "../utils/aesCrypto.utils.js";
|
|
10
|
+
import cryptoUtils from "../utils/crypto.utils.js";
|
|
11
|
+
import TLVUtil from "../tlv/tlv.utils.js";
|
|
12
|
+
import { Buffer } from 'buffer';
|
|
13
|
+
import { ec as EC } from 'elliptic';
|
|
14
|
+
|
|
15
|
+
// Register brainpool curves in elliptic's curve registry (RFC 5639).
|
|
16
|
+
// These are not built-in but are required by most EU ePassports (parameterId 13 = brainpoolP256r1).
|
|
17
|
+
(function registerBrainpoolCurves() {
|
|
18
|
+
const _elliptic = require('elliptic');
|
|
19
|
+
const _hash = require('hash.js');
|
|
20
|
+
const PresetCurve = _elliptic.curves.PresetCurve;
|
|
21
|
+
if (!_elliptic.curves.brainpoolP256r1) {
|
|
22
|
+
_elliptic.curves.brainpoolP256r1 = new PresetCurve({
|
|
23
|
+
type: 'short',
|
|
24
|
+
prime: null,
|
|
25
|
+
p: 'a9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377',
|
|
26
|
+
a: '7d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9',
|
|
27
|
+
b: '26dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b6',
|
|
28
|
+
n: 'a9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7',
|
|
29
|
+
hash: _hash.sha256,
|
|
30
|
+
gRed: false,
|
|
31
|
+
g: ['8bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262', '547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f046997']
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (!_elliptic.curves.brainpoolP384r1) {
|
|
35
|
+
_elliptic.curves.brainpoolP384r1 = new PresetCurve({
|
|
36
|
+
type: 'short',
|
|
37
|
+
prime: null,
|
|
38
|
+
p: '8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec53',
|
|
39
|
+
a: '7bc382c63d8c150c3c72080ace05afa0c2bea28e4fb22787139165efba91f90f8aa5814a503ad4eb04a8c7dd22ce2826',
|
|
40
|
+
b: '04a8c7dd22ce28268b39b55416f0447c2fb77de107dcd2a62e880ea53eeb62d57cb4390295dbc9943ab78696fa504c11',
|
|
41
|
+
n: '8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565',
|
|
42
|
+
hash: _hash.sha384,
|
|
43
|
+
gRed: false,
|
|
44
|
+
g: ['1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e826e03436d646aaef87b2e247d4af1e', '8abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280e4646217791811142820341263c5315']
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (!_elliptic.curves.brainpoolP512r1) {
|
|
48
|
+
_elliptic.curves.brainpoolP512r1 = new PresetCurve({
|
|
49
|
+
type: 'short',
|
|
50
|
+
prime: null,
|
|
51
|
+
p: 'aadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12ae6a380e62881ff2f2d82c68528aa6056583a48f3',
|
|
52
|
+
a: '7830a3318b603b89e2327145ac234cc594cbdd8d3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94ca',
|
|
53
|
+
b: '3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94cadc083e67984050b75ebae5dd2809bd638016f723',
|
|
54
|
+
n: 'aadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca70330870553e5c414ca92619418661197fac10471db1d381085ddaddb58796829ca90069',
|
|
55
|
+
hash: _hash.sha512,
|
|
56
|
+
gRed: false,
|
|
57
|
+
g: ['81aee4bdd82ed9645a21322e9c4c6a9385ed9f70b5d916c1b43b62eef4d0098eff3b1f78e2d0d48d50d1687b93b97d5f7c6d5047406a5e688b352209bcb9f822', '7dde385d566332ecc0eabfa9cf7822fdf209f70024a57b1aa000c55b881f8111b2dcde494a5f485e5bca4bd88a2763aed1ca2b2fa8f0540678cd1e0f3ad80892']
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
})();
|
|
61
|
+
const ENC_MODE = 1;
|
|
62
|
+
const MAC_MODE = 2;
|
|
63
|
+
const PACE_MODE = 3;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Implements the PACE (Password Authenticated Connection Establishment) protocol.
|
|
67
|
+
*
|
|
68
|
+
* Supports Generic Mapping (GM) with ECDH, as used by modern ePassports
|
|
69
|
+
* per ICAO Doc 9303 Part 11 and BSI TR-03110.
|
|
70
|
+
*/
|
|
71
|
+
export class PACEProtocol {
|
|
72
|
+
constructor(service, wrapper, maxTransceiveLengthForProtocol, maxTransceiveLengthForSecureMessaging, shouldCheckMAC) {
|
|
73
|
+
this.service = service;
|
|
74
|
+
this.wrapper = wrapper;
|
|
75
|
+
this.maxTransceiveLengthForProtocol = maxTransceiveLengthForProtocol;
|
|
76
|
+
this.maxTransceiveLengthForSecureMessaging = maxTransceiveLengthForSecureMessaging;
|
|
77
|
+
this.shouldCheckMAC = shouldCheckMAC;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Performs the full PACE protocol.
|
|
82
|
+
*
|
|
83
|
+
* @param accessKey the access key (MRZ or CAN based)
|
|
84
|
+
* @param oid the PACE OID from EF.CardAccess
|
|
85
|
+
* @param parameterId the standard domain parameter ID (e.g., 13 for brainpoolP256r1)
|
|
86
|
+
*/
|
|
87
|
+
async doPACE(accessKey, oid, parameterId, progressCallback) {
|
|
88
|
+
const mappingType = PACEInfo.toMappingType(oid);
|
|
89
|
+
const agreementAlg = PACEInfo.toKeyAgreementAlgorithm(oid);
|
|
90
|
+
const cipherAlg = PACEInfo.toCipherAlgorithm(oid);
|
|
91
|
+
const digestAlg = PACEInfo.toDigestAlgorithm(oid);
|
|
92
|
+
const keyLength = PACEInfo.toKeyLength(oid);
|
|
93
|
+
if (agreementAlg !== 'ECDH') {
|
|
94
|
+
throw new Error(`Only ECDH key agreement is supported, found: ${agreementAlg}`);
|
|
95
|
+
}
|
|
96
|
+
if (mappingType !== 'GM' && mappingType !== 'IM') {
|
|
97
|
+
throw new Error(`Unsupported PACE mapping type: ${mappingType}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Derive static PACE key K_pi from the access key
|
|
101
|
+
const staticPACEKeyHex = PACEProtocol.deriveStaticPACEKey(accessKey, cipherAlg, keyLength, digestAlg);
|
|
102
|
+
|
|
103
|
+
// Determine key reference
|
|
104
|
+
let paceKeyReference = EID_CONSTANTS.MRZ_PACE_KEY_REFERENCE;
|
|
105
|
+
if (accessKey instanceof PACEKeySpec) {
|
|
106
|
+
paceKeyReference = accessKey.getKeyReference();
|
|
107
|
+
}
|
|
108
|
+
const refPrivate = parameterId != null ? PACEProtocol.i2os(parameterId) : null;
|
|
109
|
+
|
|
110
|
+
// Pre-initialize curve and generate mapping key pair BEFORE any NFC I/O.
|
|
111
|
+
// brainpoolP512r1 curve init + key gen can take several seconds in JS,
|
|
112
|
+
// which would cause the ISO 14443-4 NFC link to time out if done between steps.
|
|
113
|
+
const curveName = PACEProtocol.getCurveName(parameterId ?? 13);
|
|
114
|
+
console.debug(`[PACE] Starting with OID=${oid}, parameterId=${parameterId}, ` + `curve=${curveName}, ` + `cipher=${cipherAlg}, keyLen=${keyLength}, mapping=${mappingType}`);
|
|
115
|
+
console.debug('[PACE] Pre-initializing curve and generating mapping key pair...');
|
|
116
|
+
const curve = new EC(curveName);
|
|
117
|
+
const mappingKeyPair = curve.genKeyPair();
|
|
118
|
+
console.debug('[PACE] Curve and mapping key pair ready');
|
|
119
|
+
if (progressCallback) {
|
|
120
|
+
progressCallback(5);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Step 0: MSE:Set AT
|
|
124
|
+
console.debug('[PACE] Step 0: MSE:Set AT');
|
|
125
|
+
await this.service.sendMSESetATMutualAuth(this.wrapper, oid, paceKeyReference, refPrivate);
|
|
126
|
+
console.debug('[PACE] Step 0: OK');
|
|
127
|
+
if (progressCallback) {
|
|
128
|
+
progressCallback(6);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Step 1: Encrypted Nonce
|
|
132
|
+
console.debug('[PACE] Step 1: Encrypted Nonce');
|
|
133
|
+
const piccNonce = await this.doPACEStep1(staticPACEKeyHex, cipherAlg, keyLength);
|
|
134
|
+
console.debug(`[PACE] Step 1: OK, nonce length=${piccNonce.length / 2} bytes`);
|
|
135
|
+
if (progressCallback) {
|
|
136
|
+
progressCallback(7);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Step 2: Map Nonce (GM or IM with ECDH)
|
|
140
|
+
let mappingResult;
|
|
141
|
+
console.debug(`[PACE] Step 2: Map Nonce (${mappingType})`);
|
|
142
|
+
if (mappingType === 'IM') {
|
|
143
|
+
mappingResult = await this.doPACEStep2IM(curveName, piccNonce, cipherAlg);
|
|
144
|
+
} else {
|
|
145
|
+
mappingResult = await this.doPACEStep2GM(curveName, piccNonce, mappingKeyPair);
|
|
146
|
+
}
|
|
147
|
+
console.debug(`[PACE] Step 2: OK, ephGen.x=${mappingResult.ephemeralParams.generatorX.substring(0, 16)}...`);
|
|
148
|
+
|
|
149
|
+
// Step 3: Key Agreement
|
|
150
|
+
console.debug('[PACE] Step 3: Key Agreement');
|
|
151
|
+
const {
|
|
152
|
+
pcdKeyPair,
|
|
153
|
+
piccPublicKey,
|
|
154
|
+
sharedSecret
|
|
155
|
+
} = await this.doPACEStep3(curveName, mappingResult.ephemeralParams);
|
|
156
|
+
console.debug(`[PACE] Step 3: OK, sharedSecret=${sharedSecret.substring(0, 16)}...`);
|
|
157
|
+
|
|
158
|
+
// Derive session keys
|
|
159
|
+
const encKeyHex = deriveKey(sharedSecret, cipherAlg, keyLength, ENC_MODE);
|
|
160
|
+
const macKeyHex = deriveKey(sharedSecret, cipherAlg, keyLength, MAC_MODE);
|
|
161
|
+
if (progressCallback) {
|
|
162
|
+
progressCallback(8);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Step 4: Mutual Authentication
|
|
166
|
+
console.debug('[PACE] Step 4: Mutual Authentication');
|
|
167
|
+
await this.doPACEStep4(oid, pcdKeyPair.publicKeyBytes, piccPublicKey, macKeyHex, cipherAlg, keyLength);
|
|
168
|
+
console.debug('[PACE] Step 4: OK - PACE completed successfully');
|
|
169
|
+
|
|
170
|
+
// Create secure messaging wrapper
|
|
171
|
+
let newWrapper;
|
|
172
|
+
const ssc = this.wrapper == null ? 0n : this.wrapper.getSendSequenceCounter();
|
|
173
|
+
if (cipherAlg === 'AES') {
|
|
174
|
+
newWrapper = new AESSecureMessagingWrapper(encKeyHex, macKeyHex, this.maxTransceiveLengthForSecureMessaging, this.shouldCheckMAC, ssc);
|
|
175
|
+
} else {
|
|
176
|
+
newWrapper = new TripleDesSecureMessagingWrapper(encKeyHex, macKeyHex, this.maxTransceiveLengthForSecureMessaging, this.shouldCheckMAC, 0n);
|
|
177
|
+
}
|
|
178
|
+
this.wrapper = newWrapper;
|
|
179
|
+
return new PACEResult(accessKey, mappingType, agreementAlg, cipherAlg, digestAlg, keyLength, newWrapper);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Step 1: Receive encrypted nonce from PICC, decrypt it.
|
|
184
|
+
*/
|
|
185
|
+
async doPACEStep1(staticPACEKeyHex, cipherAlg, _keyLength) {
|
|
186
|
+
// Send empty General Authenticate to receive encrypted nonce
|
|
187
|
+
const step1Response = await this.service.sendGeneralAuthenticate(this.wrapper, [], 256, false);
|
|
188
|
+
|
|
189
|
+
// Unwrap 0x80 tag to get encrypted nonce
|
|
190
|
+
const encryptedNonce = await TLVUtil.unwrapDO(0x80, step1Response);
|
|
191
|
+
const encryptedNonceHex = Buffer.from(encryptedNonce).toString('hex');
|
|
192
|
+
|
|
193
|
+
// Decrypt nonce using static PACE key
|
|
194
|
+
const zeroIV = cipherAlg === 'AES' ? '00000000000000000000000000000000' : '0000000000000000';
|
|
195
|
+
let piccNonce;
|
|
196
|
+
if (cipherAlg === 'AES') {
|
|
197
|
+
piccNonce = aesDecryptCBC(encryptedNonceHex, staticPACEKeyHex, zeroIV);
|
|
198
|
+
} else {
|
|
199
|
+
piccNonce = cryptoUtils.decryptWith3DES(encryptedNonceHex, staticPACEKeyHex);
|
|
200
|
+
}
|
|
201
|
+
return piccNonce;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Step 2: Generic Mapping with ECDH.
|
|
206
|
+
* Maps the PICC nonce to ephemeral domain parameters.
|
|
207
|
+
*/
|
|
208
|
+
async doPACEStep2GM(curveName, piccNonceHex, preGeneratedKeyPair) {
|
|
209
|
+
const curve = new EC(curveName);
|
|
210
|
+
|
|
211
|
+
// Use pre-generated mapping key pair to avoid NFC timeout
|
|
212
|
+
const mappingKeyPair = preGeneratedKeyPair;
|
|
213
|
+
const mappingPublicKey = mappingKeyPair.getPublic();
|
|
214
|
+
|
|
215
|
+
// Encode PCD mapping public key (uncompressed: 0x04 || x || y)
|
|
216
|
+
const pcdMappingPublicKeyBytes = PACEProtocol.encodeECPublicKey(mappingPublicKey.getX().toString(16), mappingPublicKey.getY().toString(16), curve);
|
|
217
|
+
|
|
218
|
+
// Send mapping data (tag 0x81)
|
|
219
|
+
const step2Data = Array.from(TLVUtil.wrapDO(0x81, Array.from(pcdMappingPublicKeyBytes)));
|
|
220
|
+
const le = step2Data.length > 233 ? EID_CONSTANTS.EXTENDED_MAX_TRANSCEIVE_LENGTH : this.maxTransceiveLengthForProtocol;
|
|
221
|
+
const step2Response = await this.service.sendGeneralAuthenticate(this.wrapper, step2Data, le, false);
|
|
222
|
+
|
|
223
|
+
// Unwrap 0x82 tag to get PICC mapping public key
|
|
224
|
+
const piccMappingPublicKeyBytes = await TLVUtil.unwrapDO(0x82, step2Response);
|
|
225
|
+
|
|
226
|
+
// Decode PICC mapping public key
|
|
227
|
+
const piccMappingPublicKey = curve.keyFromPublic(Buffer.from(piccMappingPublicKeyBytes));
|
|
228
|
+
|
|
229
|
+
// Get the full point H from the key agreement
|
|
230
|
+
const H = piccMappingPublicKey.getPublic().mul(mappingKeyPair.getPrivate());
|
|
231
|
+
|
|
232
|
+
// Map nonce: G~ = [s]G + H
|
|
233
|
+
const s = Buffer.from(piccNonceHex, 'hex');
|
|
234
|
+
const sBN = PACEProtocol.bufferToBN(s, curve);
|
|
235
|
+
const G = curve.g;
|
|
236
|
+
const sG = G.mul(sBN);
|
|
237
|
+
const ephemeralGenerator = sG.add(H);
|
|
238
|
+
return {
|
|
239
|
+
ephemeralParams: {
|
|
240
|
+
generatorX: ephemeralGenerator.getX().toString(16),
|
|
241
|
+
generatorY: ephemeralGenerator.getY().toString(16),
|
|
242
|
+
curve: curveName
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Step 2 (IM): Integrated Mapping with ECDH.
|
|
249
|
+
* PCD sends a random nonce T to the PICC; both sides independently compute
|
|
250
|
+
* the ephemeral generator via R_p(s, t) and Icart point encoding.
|
|
251
|
+
* Reference: BSI TR-03110 Part 3, Section 3.3.2 and ICAO SAC TR 2010 Section 4.4.3.3.
|
|
252
|
+
*/
|
|
253
|
+
async doPACEStep2IM(curveName, piccNonceHex, cipherAlg) {
|
|
254
|
+
if (cipherAlg !== 'AES') {
|
|
255
|
+
throw new Error(`PACE IM: Only AES cipher is supported in this implementation, found: ${cipherAlg}`);
|
|
256
|
+
}
|
|
257
|
+
const curve = new EC(curveName);
|
|
258
|
+
|
|
259
|
+
// Generate random PCD nonce T with same byte length as PICC nonce s
|
|
260
|
+
const piccNonceBytes = Buffer.from(piccNonceHex, 'hex');
|
|
261
|
+
const pcdNonceHex = cryptoUtils.getRandomValues(piccNonceBytes.length);
|
|
262
|
+
|
|
263
|
+
// Send PCD nonce T in DO 0x81 to PICC via General Authenticate
|
|
264
|
+
// The PICC response (DO 0x82) is empty per TR-SAC 3.3.2 and is ignored
|
|
265
|
+
const step2Data = Array.from(TLVUtil.wrapDO(0x81, Array.from(Buffer.from(pcdNonceHex, 'hex'))));
|
|
266
|
+
const le = step2Data.length > 233 ? EID_CONSTANTS.EXTENDED_MAX_TRANSCEIVE_LENGTH : this.maxTransceiveLengthForProtocol;
|
|
267
|
+
await this.service.sendGeneralAuthenticate(this.wrapper, step2Data, le, false);
|
|
268
|
+
|
|
269
|
+
// Compute t = R_p(s, T) via pseudo-random function
|
|
270
|
+
const p = BigInt('0x' + curve.curve.p.toString(16));
|
|
271
|
+
const tHex = PACEProtocol.pseudoRandomFunction(piccNonceHex, pcdNonceHex, p, cipherAlg);
|
|
272
|
+
|
|
273
|
+
// Map t to an elliptic curve point (new ephemeral generator) via Icart encoding
|
|
274
|
+
const {
|
|
275
|
+
x: generatorX,
|
|
276
|
+
y: generatorY
|
|
277
|
+
} = PACEProtocol.icartPointEncode(tHex, curve);
|
|
278
|
+
return {
|
|
279
|
+
ephemeralParams: {
|
|
280
|
+
generatorX,
|
|
281
|
+
generatorY,
|
|
282
|
+
curve: curveName
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Step 3: Key Agreement.
|
|
289
|
+
* Generate ephemeral key pair, exchange public keys, compute shared secret.
|
|
290
|
+
*/
|
|
291
|
+
async doPACEStep3(curveName, ephemeralParams) {
|
|
292
|
+
const curve = new EC(curveName);
|
|
293
|
+
|
|
294
|
+
// Generate ephemeral key pair on the curve with the new generator
|
|
295
|
+
// We need to use the ephemeral generator, but elliptic library
|
|
296
|
+
// works with the original curve generator.
|
|
297
|
+
// Generate a random scalar and compute public key as scalar * ephemeralGenerator
|
|
298
|
+
const privateKey = curve.genKeyPair().getPrivate();
|
|
299
|
+
const ephGen = curve.curve.point(ephemeralParams.generatorX, ephemeralParams.generatorY);
|
|
300
|
+
const pcdPublicPoint = ephGen.mul(privateKey);
|
|
301
|
+
|
|
302
|
+
// Encode PCD ephemeral public key
|
|
303
|
+
const pcdPublicKeyBytes = PACEProtocol.encodeECPoint(pcdPublicPoint.getX().toString(16), pcdPublicPoint.getY().toString(16), curve);
|
|
304
|
+
|
|
305
|
+
// Send public key (tag 0x83)
|
|
306
|
+
const step3Data = Array.from(TLVUtil.wrapDO(0x83, Array.from(pcdPublicKeyBytes)));
|
|
307
|
+
const le = step3Data.length > 233 ? EID_CONSTANTS.EXTENDED_MAX_TRANSCEIVE_LENGTH : this.maxTransceiveLengthForProtocol;
|
|
308
|
+
const step3Response = await this.service.sendGeneralAuthenticate(this.wrapper, step3Data, le, false);
|
|
309
|
+
|
|
310
|
+
// Unwrap 0x84 tag to get PICC ephemeral public key
|
|
311
|
+
const piccPublicKeyBytes = await TLVUtil.unwrapDO(0x84, step3Response);
|
|
312
|
+
|
|
313
|
+
// Decode PICC public key
|
|
314
|
+
const piccPublicPoint = curve.curve.decodePoint(Buffer.from(piccPublicKeyBytes));
|
|
315
|
+
|
|
316
|
+
// Check PCD and PICC public keys differ
|
|
317
|
+
if (pcdPublicPoint.getX().cmp(piccPublicPoint.getX()) === 0 && pcdPublicPoint.getY().cmp(piccPublicPoint.getY()) === 0) {
|
|
318
|
+
throw new Error('PCD and PICC public keys are the same');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Key agreement: K = [SK_PCD] * PK_PICC
|
|
322
|
+
const sharedSecretPoint = piccPublicPoint.mul(privateKey);
|
|
323
|
+
const sharedSecretX = sharedSecretPoint.getX().toString(16);
|
|
324
|
+
|
|
325
|
+
// Pad to full field size
|
|
326
|
+
const fieldSize = curve.curve.p.byteLength() * 2;
|
|
327
|
+
const sharedSecret = sharedSecretX.padStart(fieldSize, '0');
|
|
328
|
+
return {
|
|
329
|
+
pcdKeyPair: {
|
|
330
|
+
publicKeyBytes: pcdPublicKeyBytes,
|
|
331
|
+
privateKey
|
|
332
|
+
},
|
|
333
|
+
piccPublicKey: Uint8Array.from(piccPublicKeyBytes),
|
|
334
|
+
sharedSecret
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Step 4: Mutual Authentication.
|
|
340
|
+
* Compute and exchange authentication tokens.
|
|
341
|
+
*/
|
|
342
|
+
async doPACEStep4(oid, pcdPublicKeyBytes, piccPublicKeyBytes, macKeyHex, cipherAlg, _keyLength) {
|
|
343
|
+
// Compute PCD authentication token: T_PCD = MAC(K_mac, PK_PICC~)
|
|
344
|
+
const authInput = PACEProtocol.buildAuthenticationTokenInput(oid, piccPublicKeyBytes);
|
|
345
|
+
const pcdToken = PACEProtocol.computeAuthenticationToken(authInput, macKeyHex, cipherAlg);
|
|
346
|
+
|
|
347
|
+
// Send authentication token (tag 0x85)
|
|
348
|
+
const step4Data = Array.from(TLVUtil.wrapDO(0x85, Array.from(Buffer.from(pcdToken, 'hex'))));
|
|
349
|
+
const step4Response = await this.service.sendGeneralAuthenticate(this.wrapper, step4Data, 256, true);
|
|
350
|
+
|
|
351
|
+
// Unwrap 0x86 tag to get PICC authentication token
|
|
352
|
+
const piccTokenBytes = await TLVUtil.unwrapDO(0x86, step4Response);
|
|
353
|
+
|
|
354
|
+
// Compute expected PICC token: T_PICC = MAC(K_mac, PK_PCD~)
|
|
355
|
+
const expectedAuthInput = PACEProtocol.buildAuthenticationTokenInput(oid, pcdPublicKeyBytes);
|
|
356
|
+
const expectedPICCToken = PACEProtocol.computeAuthenticationToken(expectedAuthInput, macKeyHex, cipherAlg);
|
|
357
|
+
|
|
358
|
+
// Verify
|
|
359
|
+
const expectedBytes = Buffer.from(expectedPICCToken, 'hex');
|
|
360
|
+
const receivedBytes = Buffer.from(piccTokenBytes);
|
|
361
|
+
if (Buffer.compare(expectedBytes, receivedBytes) !== 0) {
|
|
362
|
+
throw new Error('PICC authentication token mismatch');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Build the input for the authentication token MAC.
|
|
368
|
+
* Per BSI TR-03110 Part 3 Section 4.5.3, the authentication token is computed
|
|
369
|
+
* over a public key data object wrapped in tag 0x7F49.
|
|
370
|
+
*/
|
|
371
|
+
static buildAuthenticationTokenInput(oid, publicKeyBytes) {
|
|
372
|
+
// The authentication token input is:
|
|
373
|
+
// 0x7F49 || len || 0x06 || OID_len || OID_value || 0x86 || pubkey_len || pubkey_value
|
|
374
|
+
const oidComponents = oid.split('.').map(Number);
|
|
375
|
+
const oidEncoded = PACEProtocol.encodeOIDValue(oidComponents);
|
|
376
|
+
const oidTLV = [0x06, oidEncoded.length, ...oidEncoded];
|
|
377
|
+
const pubKeyTLV = [0x86, ...PACEProtocol.encodeLengthBytes(publicKeyBytes.length), ...publicKeyBytes];
|
|
378
|
+
const innerData = [...oidTLV, ...pubKeyTLV];
|
|
379
|
+
// Wrap in 0x7F49 tag (two-byte tag)
|
|
380
|
+
const wrapped = [0x7f, 0x49, ...PACEProtocol.encodeLengthBytes(innerData.length), ...innerData];
|
|
381
|
+
return Buffer.from(wrapped).toString('hex');
|
|
382
|
+
}
|
|
383
|
+
static encodeLengthBytes(length) {
|
|
384
|
+
if (length < 0x80) {
|
|
385
|
+
return [length];
|
|
386
|
+
} else if (length < 0x100) {
|
|
387
|
+
return [0x81, length];
|
|
388
|
+
} else {
|
|
389
|
+
return [0x82, length >> 8 & 0xff, length & 0xff];
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Compute authentication token using AES-CMAC or DES MAC.
|
|
395
|
+
*/
|
|
396
|
+
static computeAuthenticationToken(dataHex, macKeyHex, cipherAlg) {
|
|
397
|
+
if (cipherAlg === 'AES') {
|
|
398
|
+
const mac = aesCMAC(dataHex, macKeyHex);
|
|
399
|
+
// Truncate to 8 bytes for authentication token
|
|
400
|
+
return mac.substring(0, 16);
|
|
401
|
+
} else {
|
|
402
|
+
return cryptoUtils.computeMac(dataHex, macKeyHex, true);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Derive the static PACE key K_pi from the access key.
|
|
408
|
+
*/
|
|
409
|
+
static deriveStaticPACEKey(accessKey, cipherAlg, keyLength, _digestAlg) {
|
|
410
|
+
let keySeed;
|
|
411
|
+
if ('getDocumentNumber' in accessKey && typeof accessKey.getDocumentNumber === 'function') {
|
|
412
|
+
// BAC key spec - use MRZ-derived key seed
|
|
413
|
+
keySeed = accessKey.getKey();
|
|
414
|
+
} else if (accessKey instanceof PACEKeySpec) {
|
|
415
|
+
keySeed = accessKey.getKey();
|
|
416
|
+
} else {
|
|
417
|
+
keySeed = accessKey.getKey();
|
|
418
|
+
}
|
|
419
|
+
return deriveKey(keySeed, cipherAlg, keyLength, PACE_MODE);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// --- EC Utility Methods ---
|
|
423
|
+
|
|
424
|
+
static encodeECPublicKey(xHex, yHex, curve) {
|
|
425
|
+
return PACEProtocol.encodeECPoint(xHex, yHex, curve);
|
|
426
|
+
}
|
|
427
|
+
static encodeECPoint(xHex, yHex, curve) {
|
|
428
|
+
const fieldSize = curve.curve.p.byteLength();
|
|
429
|
+
const x = xHex.padStart(fieldSize * 2, '0');
|
|
430
|
+
const y = yHex.padStart(fieldSize * 2, '0');
|
|
431
|
+
const uncompressed = '04' + x + y;
|
|
432
|
+
return Uint8Array.from(Buffer.from(uncompressed, 'hex'));
|
|
433
|
+
}
|
|
434
|
+
static bufferToBN(buf, curve) {
|
|
435
|
+
// Convert buffer to BN used by elliptic library
|
|
436
|
+
const hex = buf.toString('hex');
|
|
437
|
+
return curve.keyFromPrivate(hex).getPrivate();
|
|
438
|
+
}
|
|
439
|
+
static encodeOIDValue(components) {
|
|
440
|
+
if (components.length < 2) {
|
|
441
|
+
throw new Error('OID must have at least 2 components');
|
|
442
|
+
}
|
|
443
|
+
const encoded = [];
|
|
444
|
+
encoded.push(components[0] * 40 + components[1]);
|
|
445
|
+
for (let i = 2; i < components.length; i++) {
|
|
446
|
+
const value = components[i];
|
|
447
|
+
if (value < 128) {
|
|
448
|
+
encoded.push(value);
|
|
449
|
+
} else {
|
|
450
|
+
const bytes = [];
|
|
451
|
+
let v = value;
|
|
452
|
+
bytes.push(v & 0x7f);
|
|
453
|
+
v >>= 7;
|
|
454
|
+
while (v > 0) {
|
|
455
|
+
bytes.push(v & 0x7f | 0x80);
|
|
456
|
+
v >>= 7;
|
|
457
|
+
}
|
|
458
|
+
bytes.reverse();
|
|
459
|
+
encoded.push(...bytes);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return encoded;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ---- PACE IM helpers ----
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Fast modular exponentiation using BigInt (square-and-multiply).
|
|
469
|
+
*/
|
|
470
|
+
static modPow(base, exp, mod) {
|
|
471
|
+
if (mod === 1n) return 0n;
|
|
472
|
+
let result = 1n;
|
|
473
|
+
base = (base % mod + mod) % mod;
|
|
474
|
+
while (exp > 0n) {
|
|
475
|
+
if (exp & 1n) {
|
|
476
|
+
result = result * base % mod;
|
|
477
|
+
}
|
|
478
|
+
exp >>= 1n;
|
|
479
|
+
base = base * base % mod;
|
|
480
|
+
}
|
|
481
|
+
return result;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Pseudo-random function R_p(s, t) for PACE Integrated Mapping.
|
|
486
|
+
* Specified in ICAO Doc 9303 Part 11, Section 4.4.3.3.2 and BSI TR-03110.
|
|
487
|
+
*
|
|
488
|
+
* Uses the block cipher in CBC mode (IV=0) to derive a field element mod p.
|
|
489
|
+
*
|
|
490
|
+
* @param sHex - PICC nonce s (hex)
|
|
491
|
+
* @param tHex - PCD nonce t (hex, same length as s)
|
|
492
|
+
* @param p - prime of the field GF(p)
|
|
493
|
+
* @param cipherAlg - 'AES'
|
|
494
|
+
* @returns field element as hex string (big-endian, same byte length as s)
|
|
495
|
+
*/
|
|
496
|
+
static pseudoRandomFunction(sHex, tHex, p, cipherAlg) {
|
|
497
|
+
if (cipherAlg !== 'AES') {
|
|
498
|
+
throw new Error(`PACE IM pseudoRandomFunction: unsupported cipher ${cipherAlg}`);
|
|
499
|
+
}
|
|
500
|
+
const l = sHex.length / 2 * 8; // bit length of s
|
|
501
|
+
|
|
502
|
+
// Constants from ICAO Doc 9303 Part 11, Table 4 (also BSI TR-03110)
|
|
503
|
+
let c0Hex;
|
|
504
|
+
let c1Hex;
|
|
505
|
+
if (l === 128) {
|
|
506
|
+
c0Hex = 'a668892a7c41e3ca739f40b057d85904';
|
|
507
|
+
c1Hex = 'a4e136ac725f738b01c1f60217c188ad';
|
|
508
|
+
} else if (l === 192 || l === 256) {
|
|
509
|
+
c0Hex = 'd463d65234124ef7897054986dca0a174e28df758cbaa03f240616414d5a1676';
|
|
510
|
+
c1Hex = '54bd7255f0aaf831bec3423fcf39d69b6cbf066677d0faae5aadd99df8e53517';
|
|
511
|
+
} else {
|
|
512
|
+
throw new Error(`PACE IM pseudoRandomFunction: unsupported nonce length ${l} bits`);
|
|
513
|
+
}
|
|
514
|
+
const zeroIV = '00000000000000000000000000000000'; // 16-byte zero IV
|
|
515
|
+
|
|
516
|
+
// Initial key: key_0 = E_T(S) — encrypt S with key T, AES-CBC, IV=0
|
|
517
|
+
let keyHex = aesEncryptCBC(sHex, tHex, zeroIV);
|
|
518
|
+
|
|
519
|
+
// Generate output blocks: for each i, key_{i+1} = E_key_i(c0), x_i = E_key_i(c1)
|
|
520
|
+
// Repeat until n blocks of l bits cover p.bitLength + 64 bits
|
|
521
|
+
const pBitLength = p.toString(2).length;
|
|
522
|
+
const outputBlocks = [];
|
|
523
|
+
let n = 0;
|
|
524
|
+
while (n * l < pBitLength + 64) {
|
|
525
|
+
const newKeyHex = aesEncryptCBC(c0Hex, keyHex, zeroIV);
|
|
526
|
+
const xiHex = aesEncryptCBC(c1Hex, keyHex, zeroIV);
|
|
527
|
+
keyHex = newKeyHex;
|
|
528
|
+
outputBlocks.push(xiHex);
|
|
529
|
+
n++;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// x = (x_1 || x_2 || ... || x_n) mod p, returned as field-element-sized hex
|
|
533
|
+
const xBigInt = BigInt('0x' + outputBlocks.join(''));
|
|
534
|
+
const result = xBigInt % p;
|
|
535
|
+
const fieldBytes = sHex.length / 2;
|
|
536
|
+
return result.toString(16).padStart(fieldBytes * 2, '0');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Icart's deterministic encoding of a field element to a curve point.
|
|
541
|
+
* Maps an element t ∈ GF(p) to a point (x, y) on y² = x³ + ax + b.
|
|
542
|
+
* Requires p ≡ 3 (mod 4).
|
|
543
|
+
* Reference: ICAO SAC TR 2010, Section 5.2.
|
|
544
|
+
*
|
|
545
|
+
* @param tHex - field element as hex string
|
|
546
|
+
* @param curve - the elliptic curve
|
|
547
|
+
* @returns { x, y } as hex strings (field-element-width, big-endian)
|
|
548
|
+
*/
|
|
549
|
+
static icartPointEncode(tHex, curve) {
|
|
550
|
+
const p = BigInt('0x' + curve.curve.p.toString(16));
|
|
551
|
+
// curve.curve.a and .b are stored in Montgomery (red) form in elliptic —
|
|
552
|
+
// must call fromRed() to get the actual field element values.
|
|
553
|
+
const a = BigInt('0x' + curve.curve.a.fromRed().toString(16));
|
|
554
|
+
const b = BigInt('0x' + curve.curve.b.fromRed().toString(16));
|
|
555
|
+
if (p % 4n !== 3n) {
|
|
556
|
+
throw new Error('PACE IM Icart encoding requires p ≡ 3 (mod 4)');
|
|
557
|
+
}
|
|
558
|
+
const t = BigInt('0x' + tHex);
|
|
559
|
+
// Helper: reduce x into [0, p) handling negative intermediate values
|
|
560
|
+
const mod = x => (x % p + p) % p;
|
|
561
|
+
|
|
562
|
+
// 1. alpha = -t² mod p
|
|
563
|
+
const alpha = mod(-(t * t % p));
|
|
564
|
+
|
|
565
|
+
// 2. x2 = -b * (1 + alpha + alpha²) / (a * (alpha + alpha²)) mod p
|
|
566
|
+
const alphaSq = alpha * alpha % p;
|
|
567
|
+
const alphaPlusAlphaSq = (alpha + alphaSq) % p;
|
|
568
|
+
const onePlusAlphaPlusAlphaSq = (1n + alphaPlusAlphaSq) % p;
|
|
569
|
+
const aTimesAlphaPlusAlphaSq = a * alphaPlusAlphaSq % p;
|
|
570
|
+
// Modular inverse via Fermat's little theorem: a^(p-2) mod p
|
|
571
|
+
const inv = PACEProtocol.modPow(aTimesAlphaPlusAlphaSq, p - 2n, p);
|
|
572
|
+
const x2 = mod(-(b * onePlusAlphaPlusAlphaSq % p) * inv % p);
|
|
573
|
+
|
|
574
|
+
// 3. x3 = alpha * x2 mod p
|
|
575
|
+
const x3 = alpha * x2 % p;
|
|
576
|
+
|
|
577
|
+
// 4. h2 = x2³ + a*x2 + b mod p
|
|
578
|
+
const h2 = mod(x2 * x2 % p * x2 % p + a * x2 % p + b);
|
|
579
|
+
|
|
580
|
+
// 5. u = t³ * h2 mod p
|
|
581
|
+
const u = t * t % p * t % p * h2 % p;
|
|
582
|
+
|
|
583
|
+
// 6. aa = h2^(p-1-(p+1)/4) mod p (related to square root formula for p ≡ 3 mod 4)
|
|
584
|
+
const ppo4 = (p + 1n) / 4n;
|
|
585
|
+
const aa = PACEProtocol.modPow(h2, p - 1n - ppo4, p);
|
|
586
|
+
|
|
587
|
+
// 7. Check if h2 is a quadratic residue (aa² * h2 == 1), choose (x,y) accordingly
|
|
588
|
+
const aaSqTimesH2 = PACEProtocol.modPow(aa, 2n, p) * h2 % p;
|
|
589
|
+
let resX;
|
|
590
|
+
let resY;
|
|
591
|
+
if (aaSqTimesH2 === 1n) {
|
|
592
|
+
resX = x2;
|
|
593
|
+
resY = aa * h2 % p;
|
|
594
|
+
} else {
|
|
595
|
+
resX = x3;
|
|
596
|
+
resY = aa * u % p;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// All PACE curves (NIST P-*, brainpool) have cofactor = 1, no cofactor multiplication needed
|
|
600
|
+
const fieldHexLen = Math.ceil(p.toString(16).length / 2) * 2;
|
|
601
|
+
return {
|
|
602
|
+
x: resX.toString(16).padStart(fieldHexLen, '0'),
|
|
603
|
+
y: resY.toString(16).padStart(fieldHexLen, '0')
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
static i2os(value) {
|
|
607
|
+
if (value === 0) return [0];
|
|
608
|
+
const bytes = [];
|
|
609
|
+
let v = value;
|
|
610
|
+
while (v > 0) {
|
|
611
|
+
bytes.unshift(v & 0xff);
|
|
612
|
+
v >>= 8;
|
|
613
|
+
}
|
|
614
|
+
return bytes;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Map standard domain parameter ID to elliptic curve name.
|
|
619
|
+
*/
|
|
620
|
+
static getCurveName(parameterId) {
|
|
621
|
+
// BSI TR-03110 Table D.2 / ICAO Doc 9303 standardized domain parameters
|
|
622
|
+
switch (parameterId) {
|
|
623
|
+
case 8:
|
|
624
|
+
return 'p192';
|
|
625
|
+
// NIST P-192
|
|
626
|
+
case 9:
|
|
627
|
+
return 'brainpoolP192r1';
|
|
628
|
+
case 10:
|
|
629
|
+
return 'p224';
|
|
630
|
+
// NIST P-224
|
|
631
|
+
case 11:
|
|
632
|
+
return 'brainpoolP224r1';
|
|
633
|
+
case 12:
|
|
634
|
+
return 'p256';
|
|
635
|
+
// NIST P-256
|
|
636
|
+
case 13:
|
|
637
|
+
return 'brainpoolP256r1';
|
|
638
|
+
// Most common in EU passports
|
|
639
|
+
case 14:
|
|
640
|
+
return 'brainpoolP320r1';
|
|
641
|
+
case 15:
|
|
642
|
+
return 'p384';
|
|
643
|
+
// NIST P-384
|
|
644
|
+
case 16:
|
|
645
|
+
return 'brainpoolP384r1';
|
|
646
|
+
case 17:
|
|
647
|
+
return 'brainpoolP512r1';
|
|
648
|
+
case 18:
|
|
649
|
+
return 'p521';
|
|
650
|
+
// NIST P-521
|
|
651
|
+
default:
|
|
652
|
+
throw new Error(`Unsupported domain parameter ID: ${parameterId}`);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Result of PACE protocol execution.
|
|
5
|
+
*/
|
|
6
|
+
export class PACEResult {
|
|
7
|
+
constructor(paceKey, mappingType, agreementAlg, cipherAlg, digestAlg, keyLength, wrapper) {
|
|
8
|
+
this.paceKey = paceKey;
|
|
9
|
+
this.mappingType = mappingType;
|
|
10
|
+
this.agreementAlg = agreementAlg;
|
|
11
|
+
this.cipherAlg = cipherAlg;
|
|
12
|
+
this.digestAlg = digestAlg;
|
|
13
|
+
this.keyLength = keyLength;
|
|
14
|
+
this.wrapper = wrapper;
|
|
15
|
+
}
|
|
16
|
+
getPACEKey() {
|
|
17
|
+
return this.paceKey;
|
|
18
|
+
}
|
|
19
|
+
getMappingType() {
|
|
20
|
+
return this.mappingType;
|
|
21
|
+
}
|
|
22
|
+
getAgreementAlg() {
|
|
23
|
+
return this.agreementAlg;
|
|
24
|
+
}
|
|
25
|
+
getCipherAlg() {
|
|
26
|
+
return this.cipherAlg;
|
|
27
|
+
}
|
|
28
|
+
getDigestAlg() {
|
|
29
|
+
return this.digestAlg;
|
|
30
|
+
}
|
|
31
|
+
getKeyLength() {
|
|
32
|
+
return this.keyLength;
|
|
33
|
+
}
|
|
34
|
+
getWrapper() {
|
|
35
|
+
return this.wrapper;
|
|
36
|
+
}
|
|
37
|
+
}
|