SimulatedIcaoChip constructor
SimulatedIcaoChip({
- IcaoDocumentType type = IcaoDocumentType.passport,
- String? documentNumber,
- String issuingState = 'UTO',
- String nationality = 'UTO',
- String lastName = 'SPECIMEN',
- String firstNames = 'ALICE MARIE',
- PartialDate? birthDate,
- Sex sex = Sex.female,
- DateTime? expiryDate,
- String? birthPlace = 'ZENITH<UTOPIA',
- String? address = 'AVENUE DES NATIONS 1<1000 ZENITH<UTOPIA',
- String? issuingAuthority = 'MINISTRY OF FOREIGN AFFAIRS',
- DateTime? issueDate,
- String? personalNumber,
- Uint8List? photo,
- IcaoFaceEncoding faceEncoding = IcaoFaceEncoding.iso19794,
- String can = '123456',
- SimulatedChipAccess access = SimulatedChipAccess.paceAndBac,
- SimulatedChipProof proof = SimulatedChipProof.chipAuthentication,
- bool cloned = false,
- IcaoDataGroup? tamperedGroup,
- Duration latency = Duration.zero,
A chip holding the given data, by default that of a fictional citizen.
Names are given as in the MRZ, upper case. birthPlace and address
go into DG11, issuingAuthority and issueDate into DG12. A
personalNumber goes into the optional data of the MRZ, as Belgium
puts the national number there, and into DG11.
tamperedGroup is changed after signing, so Passive Authentication
fails. A cloned chip fails its proof. latency delays every
answer.
Implementation
SimulatedIcaoChip({
this.type = IcaoDocumentType.passport,
String? documentNumber,
String issuingState = 'UTO',
String nationality = 'UTO',
String lastName = 'SPECIMEN',
String firstNames = 'ALICE MARIE',
PartialDate? birthDate,
Sex sex = Sex.female,
DateTime? expiryDate,
String? birthPlace = 'ZENITH<UTOPIA',
String? address = 'AVENUE DES NATIONS 1<1000 ZENITH<UTOPIA',
String? issuingAuthority = 'MINISTRY OF FOREIGN AFFAIRS',
DateTime? issueDate,
String? personalNumber,
Uint8List? photo,
this.faceEncoding = IcaoFaceEncoding.iso19794,
this.can = '123456',
this.access = SimulatedChipAccess.paceAndBac,
this.proof = SimulatedChipProof.chipAuthentication,
this.cloned = false,
IcaoDataGroup? tamperedGroup,
this.latency = Duration.zero,
}) : documentNumber = documentNumber ??
(type == IcaoDocumentType.passport ? 'UT1234567' : 'UTD123456'),
birthDate = birthDate ?? PartialDate(1990, 5, 15),
expiryDate = expiryDate ?? DateTime.utc(2034, 3, 13) {
final mrz = _mrz(
type: type,
number: this.documentNumber,
state: issuingState,
nationality: nationality,
lastName: lastName,
firstNames: firstNames,
birthDate: this.birthDate,
sex: sex,
expiryDate: this.expiryDate,
personalNumber: personalNumber ?? '',
);
final face = photo ?? specimenPhoto;
final groups = <IcaoDataGroup, Uint8List>{
IcaoDataGroup.dg1: tlv(0x61, tlv(0x5F1F, ascii.encode(mrz))),
IcaoDataGroup.dg2: _dg2(face, faceEncoding, sex),
IcaoDataGroup.dg11: _dg11(
name: '${lastName.replaceAll(' ', '<')}<<'
'${firstNames.replaceAll(' ', '<')}',
birthDate: this.birthDate,
birthPlace: birthPlace,
address: address,
personalNumber: personalNumber,
),
IcaoDataGroup.dg12: _dg12(
issuingAuthority,
issueDate ?? DateTime.utc(2024, 3, 14),
),
};
final dg14 = <Uint8List>[];
if (access != SimulatedChipAccess.bacOnly) {
dg14.add(_paceInfo(_paceOid));
}
switch (proof) {
case SimulatedChipProof.chipAuthentication:
dg14
..add(_chipAuthenticationInfo)
..add(_chipAuthenticationKeyInfo);
case SimulatedChipProof.activeAuthenticationEc:
dg14.add(derSequence([
derObjectIdentifier(activeAuthenticationOid),
derInteger(BigInt.one),
derObjectIdentifier('0.4.0.127.0.7.1.1.4.1.3'),
]));
groups[IcaoDataGroup.dg15] = tlv(
0x6F,
SimulatedPki.subjectPublicKeyInfo(
SimulatedPki.publicKeyOf(_activeEcKey),
),
);
case SimulatedChipProof.activeAuthenticationRsa:
groups[IcaoDataGroup.dg15] =
tlv(0x6F, rsaSubjectPublicKeyInfo(_rsaKey));
case SimulatedChipProof.chipAuthenticationMapping:
case SimulatedChipProof.none:
}
if (dg14.isNotEmpty) groups[IcaoDataGroup.dg14] = tlv(0x6E, derSet(dg14));
final pki = SimulatedPki.instance;
final sorted = groups.keys.toList()
..sort((a, b) => a.number.compareTo(b.number));
final lds = derSequence([
derInteger(BigInt.zero),
derSequence([
derObjectIdentifier(HashAlgorithm.sha256.objectIdentifier),
]),
derSequence([
for (final group in sorted)
derSequence([
derInteger(BigInt.from(group.number)),
derOctetString(HashAlgorithm.sha256.digest(groups[group]!)),
]),
]),
]);
final sod = tlv(0x77, pki.signedData('2.23.136.1.1.1', lds));
if (tamperedGroup != null && groups[tamperedGroup] != null) {
final file = Uint8List.fromList(groups[tamperedGroup]!);
file[file.length - 1] ^= 0x01;
groups[tamperedGroup] = file;
}
_application = {
0x011E: tlv(
0x60,
concat([
tlv(0x5F01, ascii.encode('0107')),
tlv(0x5F36, ascii.encode('040000')),
tlv(0x5C, [for (final group in sorted) group.tag]),
]),
),
0x011D: sod,
for (final group in sorted) group.fileId: groups[group]!,
};
_master = {
if (access != SimulatedChipAccess.bacOnly)
0x011C: derSet([_paceInfo(_paceOid)]),
if (proof == SimulatedChipProof.chipAuthenticationMapping)
0x011D: pki.signedData(
'0.4.0.127.0.7.3.2.1',
derSet([
_paceInfo(_paceOid),
_chipAuthenticationInfo,
_chipAuthenticationKeyInfo,
]),
),
};
_mrzKey = IcaoMrzKey(
documentNumber: this.documentNumber,
birthDate: this.birthDate,
expiryDate: this.expiryDate,
);
}