From ab73ee62600fbe15275954b1891be18c2ed25fae Mon Sep 17 00:00:00 2001
From: Lizard
Date: Thu, 29 Aug 2024 20:50:09 +0600
Subject: [PATCH] feat: v0.1.2 | Add support for DG11 and DG12
---
README.md | 11 ++--
jsr.json | 2 +-
package.json | 2 +-
src/consts/interfaces.ts | 52 +++++++++++++++++
src/dg11.ts | 119 +++++++++++++++++++++++++++++++++++++++
src/dg12.ts | 93 ++++++++++++++++++++++++++++++
src/dg2.ts | 10 ++++
src/dg3.ts | 10 ++++
src/index.ts | 2 +
tests/dgs/EF_DG11.bin | Bin 0 -> 139 bytes
tests/dgs/EF_DG12.bin | Bin 0 -> 69 bytes
tests/index.test.ts | 32 ++++++++++-
12 files changed, 325 insertions(+), 8 deletions(-)
create mode 100644 src/dg11.ts
create mode 100644 src/dg12.ts
create mode 100644 tests/dgs/EF_DG11.bin
create mode 100644 tests/dgs/EF_DG12.bin
diff --git a/README.md b/README.md
index b0e78cb..5a0dd37 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,9 @@
-
+
+
+
@@ -26,7 +28,7 @@ npx jsr add @li0ard/tsemrtd # for npm
## Usage
```ts
-import { DG1 } from "tsemrtd"
+import { DG1 } from "@li0ard/tsemrtd"
let file = await Bun.file("EF_DG1.bin").bytes()
let data = DG1.load(Buffer.from(file))
@@ -43,6 +45,5 @@ console.log(data)
- [x] DG3
- [x] DG5
- [x] DG7
-- [ ] DG11
-- [ ] DG12
-- [ ] DG16
+- [x] DG11
+- [x] DG12
\ No newline at end of file
diff --git a/jsr.json b/jsr.json
index 3eb459f..f68b8dc 100644
--- a/jsr.json
+++ b/jsr.json
@@ -1,6 +1,6 @@
{
"name": "@li0ard/tsemrtd",
- "version": "0.1.1",
+ "version": "0.1.2",
"exports": "./src/index.ts",
"publish": {
"include": [
diff --git a/package.json b/package.json
index 5099324..3f7fb39 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "tsemrtd",
"module": "index.ts",
- "version": "0.1.1",
+ "version": "0.1.2",
"type": "module",
"author": "li0ard",
"repository": {
diff --git a/src/consts/interfaces.ts b/src/consts/interfaces.ts
index 5923d36..de7d42b 100644
--- a/src/consts/interfaces.ts
+++ b/src/consts/interfaces.ts
@@ -87,4 +87,56 @@ export interface DecodedFingerprint extends AbstractBioTemplate {
nrOfRepresention: number,
/** Name of finger/part of palm */
fingerType: FingerType
+}
+
+/** Decoded EF.DG11 datagroup */
+export interface DecodedAdditionalPersonalData {
+ /** Full name of document holder */
+ nameOfHolder: string,
+ /** Other names of document holder */
+ otherNames: string[],
+ /** Personal number */
+ personalNumber: string,
+ /** Full date of birth (CCYYMMDD) */
+ fullDateOfBirth: number,
+ /** Place of birth */
+ placeOfBirth: string[],
+ /** Permanent residence address */
+ permanentAddress: string[],
+ /** Phone number */
+ telephone: string,
+ /** Profession */
+ profession: string,
+ /** Position */
+ title: string,
+ /** Personal resume */
+ personalSummary: string,
+ /** Proof of citizenship. Image described by ISO/IEC 10918 */
+ proofOfCitizenship: Buffer,
+ /** Numbers of other valid TDs */
+ otherValidTDNumbers: string[],
+ /** Information about detention */
+ custodyInformation: string
+}
+
+/** Decoded EF.DG12 datagroup */
+export interface DecodedAdditionalDocumentData {
+ /** Full date of issue (YYYYMMDD) */
+ dateOfIssue: number,
+ /** Issuing authority */
+ issuingAuthority: string,
+ /** Name of another person, formatted according to the rules of Doc 9303 */
+ namesOfOtherPersons: string[],
+ /** Endorsements and observations */
+ endorsements: string,
+ /** Tax and exit requirements */
+ taxAndExitReqs: string,
+ /** Image of front of document. Image described by ISO/IEC 10918 */
+ imageOfFront: Buffer,
+ /** Image of rear of document. Image described by ISO/IEC 10918 */
+ imageOfRear: Buffer,
+ /** Date and time of document personalization (YYYYMMDDHHMMSS) */
+ dateOfPersonalization: number,
+ /** Serial number of personalization system */
+ personalizationNumber: string
}
\ No newline at end of file
diff --git a/src/dg11.ts b/src/dg11.ts
new file mode 100644
index 0000000..c2107ea
--- /dev/null
+++ b/src/dg11.ts
@@ -0,0 +1,119 @@
+import TLV from "node-tlv"
+import { Enums, Interfaces } from "./index";
+
+/**
+ * Class for working with DG11 (Additional personal data)
+*/
+export class DG11 {
+ /**
+ * Get additional personal data
+ * @param data Data of EF.DG11 file
+ */
+ static load(data: string | Buffer): Interfaces.DecodedAdditionalPersonalData {
+ const FULL_NAME_TAG = 0x5F0E;
+ const OTHER_NAME_TAG = 0x5F0F;
+ const OTHER_NAME_ARRAY_TAG = 0xA0;
+ const PERSONAL_NUMBER_TAG = 0x5F10;
+
+ // In 'CCYYMMDD' format.
+ const FULL_DATE_OF_BIRTH_TAG = 0x5F2B;
+
+ // Fields separated by '<'
+ const PLACE_OF_BIRTH_TAG = 0x5F11;
+
+ // Fields separated by '<'
+ const PERMANENT_ADDRESS_TAG = 0x5F42;
+ const TELEPHONE_TAG = 0x5F12;
+ const PROFESSION_TAG = 0x5F13;
+ const TITLE_TAG = 0x5F14;
+ const PERSONAL_SUMMARY_TAG = 0x5F15;
+
+ // Compressed image per ISO/IEC 10918
+ const PROOF_OF_CITIZENSHIP_TAG = 0x5F16;
+
+ // Separated by '<'
+ const OTHER_VALID_TD_NUMBERS_TAG = 0x5F17;
+ const CUSTODY_INFORMATION_TAG = 0x5F18;
+
+ let nameOfHolder: string = "",
+ otherNames: string[] = [],
+ personalNumber: string = "",
+ fullDateOfBirth: number = 0,
+ placeOfBirth: string[] = [],
+ permanentAddress: string[] = [],
+ telephone: string = "",
+ profession: string = "",
+ title: string = "",
+ personalSummary: string = "",
+ proofOfCitizenship: Buffer = Buffer.from(""),
+ otherValidTDNumbers: string[] = [],
+ custodyInformation: string = "";
+
+
+ let tlv = TLV.parse(data)
+ if(parseInt(tlv.tag, 16) != Enums.TAGS.DG11) throw new Error(`Invalid DG1 tag "0x${tlv.tag}", expected 0x${Enums.TAGS.DG11.toString(16)}`);
+
+ for(let i of tlv.child) {
+ switch(parseInt(i.tag, 16)) {
+ case FULL_NAME_TAG:
+ nameOfHolder = i.bValue.toString("utf-8")
+ break;
+ case PERSONAL_NUMBER_TAG:
+ personalNumber = i.bValue.toString("utf-8")
+ break;
+ case OTHER_NAME_ARRAY_TAG:
+ for(let j of i.child) {
+ if(parseInt(j.tag, 16) == OTHER_NAME_TAG) {
+ otherNames.push(j.bValue.toString("utf-8"))
+ }
+ }
+ break;
+ case FULL_DATE_OF_BIRTH_TAG:
+ fullDateOfBirth = parseInt(i.bValue.toString("hex"))
+ break;
+ case PLACE_OF_BIRTH_TAG:
+ placeOfBirth = i.bValue.toString("utf-8").split("<")
+ break;
+ case PERMANENT_ADDRESS_TAG:
+ permanentAddress = i.bValue.toString("utf-8").split("<")
+ break;
+ case TELEPHONE_TAG:
+ telephone = i.bValue.toString("utf-8")
+ break
+ case PROFESSION_TAG:
+ profession = i.bValue.toString("utf-8")
+ break;
+ case TITLE_TAG:
+ title = i.bValue.toString("utf-8")
+ break;
+ case PERSONAL_SUMMARY_TAG:
+ personalSummary = i.bValue.toString("utf-8")
+ break;
+ case PROOF_OF_CITIZENSHIP_TAG:
+ proofOfCitizenship = i.bValue
+ break;
+ case OTHER_VALID_TD_NUMBERS_TAG:
+ otherValidTDNumbers = i.bValue.toString("utf-8").split("<")
+ break;
+ case CUSTODY_INFORMATION_TAG:
+ custodyInformation = i.bValue.toString("utf-8")
+ break;
+ }
+ }
+ return {
+ nameOfHolder,
+ otherNames,
+ personalNumber,
+ fullDateOfBirth,
+ placeOfBirth,
+ permanentAddress,
+ telephone,
+ profession,
+ title,
+ personalSummary,
+ proofOfCitizenship,
+ otherValidTDNumbers,
+ custodyInformation
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/dg12.ts b/src/dg12.ts
new file mode 100644
index 0000000..0278cd7
--- /dev/null
+++ b/src/dg12.ts
@@ -0,0 +1,93 @@
+import TLV from "node-tlv"
+import { Enums, Interfaces } from "./index";
+
+/**
+ * Class for working with DG12 (Additional document data)
+*/
+export class DG12 {
+ /**
+ * Get additional document data
+ * @param data Data of EF.DG12 file
+ */
+ static load(data: string | Buffer): Interfaces.DecodedAdditionalDocumentData {
+ const ISSUING_AUTHORITY_TAG = 0x5F19;
+
+ // yyyymmdd
+ const DATE_OF_ISSUE_TAG = 0x5F26;
+
+ // formatted per ICAO 9303 rules
+ const NAME_OF_OTHER_PERSON_ARRAY_TAG = 0xA0;
+ const NAME_OF_OTHER_PERSON_TAG = 0x5F1A;
+ const ENDORSEMENTS_AND_OBSERVATIONS_TAG = 0x5F1B;
+ const TAX_OR_EXIT_REQUIREMENTS_TAG = 0x5F1C;
+
+ // Image per ISO/IEC 10918
+ const IMAGE_OF_FRONT_TAG = 0x5F1D;
+ const IMAGE_OF_REAR_TAG = 0x5F1E;
+
+ // yyyymmddhhmmss
+ const DATE_AND_TIME_OF_PERSONALIZATION = 0x5F55;
+ const PERSONALIZATION_SYSTEM_SERIAL_NUMBER_TAG = 0x5F56;
+
+ let dateOfIssue: number = 0,
+ issuingAuthority: string = "",
+ namesOfOtherPersons: string[] = [],
+ endorsements: string = "",
+ taxAndExitReqs: string = "",
+ imageOfFront: Buffer = Buffer.from(""),
+ imageOfRear: Buffer = Buffer.from(""),
+ dateOfPersonalization: number = 0,
+ personalizationNumber: string = ""
+
+ let tlv = TLV.parse(data)
+ if(parseInt(tlv.tag, 16) != Enums.TAGS.DG12) throw new Error(`Invalid DG1 tag "0x${tlv.tag}", expected 0x${Enums.TAGS.DG12.toString(16)}`);
+
+ for(let i of tlv.child) {
+ switch(parseInt(i.tag, 16)) {
+ case ISSUING_AUTHORITY_TAG:
+ issuingAuthority = i.bValue.toString("utf-8")
+ break;
+ case DATE_OF_ISSUE_TAG:
+ dateOfIssue = parseInt(i.bValue.toString("hex"))
+ break;
+ case NAME_OF_OTHER_PERSON_ARRAY_TAG:
+ for(let j of i.child) {
+ if(parseInt(j.tag, 16) == NAME_OF_OTHER_PERSON_TAG) {
+ namesOfOtherPersons.push(j.bValue.toString("utf-8"))
+ }
+ }
+ break;
+ case ENDORSEMENTS_AND_OBSERVATIONS_TAG:
+ endorsements = i.bValue.toString("utf-8")
+ break;
+ case TAX_OR_EXIT_REQUIREMENTS_TAG:
+ taxAndExitReqs = i.bValue.toString("utf-8")
+ break;
+ case IMAGE_OF_FRONT_TAG:
+ imageOfFront = i.bValue;
+ break;
+ case IMAGE_OF_REAR_TAG:
+ imageOfRear = i.bValue;
+ break;
+ case DATE_AND_TIME_OF_PERSONALIZATION:
+ dateOfPersonalization = parseInt(i.bValue.toString("hex"))
+ break;
+ case PERSONALIZATION_SYSTEM_SERIAL_NUMBER_TAG:
+ personalizationNumber = i.bValue.toString("utf-8")
+ break;
+ }
+ }
+
+ return {
+ dateOfIssue,
+ issuingAuthority,
+ namesOfOtherPersons,
+ endorsements,
+ taxAndExitReqs,
+ imageOfFront,
+ imageOfRear,
+ dateOfPersonalization,
+ personalizationNumber
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/dg2.ts b/src/dg2.ts
index 79ed5c4..9c726b6 100644
--- a/src/dg2.ts
+++ b/src/dg2.ts
@@ -5,6 +5,12 @@ import { Enums, Interfaces } from "./index";
* Class for working with DG2 (Face)
*/
export class DG2 {
+ /**
+ * Extract int from buffer
+ * @param data Buffer
+ * @param start Offset
+ * @param end Offset+length
+ */
private extractContent(data: Buffer, start: number, end: number): number {
if (end - start == 1) {
return data.subarray(start, end).readInt8();
@@ -13,6 +19,10 @@ export class DG2 {
}
return data.subarray(start,end).readInt32BE()
}
+ /**
+ * Read Biometric data block
+ * @param tlv
+ */
readBDB(tlv: TLV): Interfaces.DecodedImage {
if(parseInt(tlv.tag, 16) != 0x7f60) throw new Error(`Invalid object tag "0x${tlv.tag}", expected 0x7f60`);
diff --git a/src/dg3.ts b/src/dg3.ts
index 81ea7a9..762d5e6 100644
--- a/src/dg3.ts
+++ b/src/dg3.ts
@@ -6,6 +6,12 @@ import { Enums, Interfaces } from "./index";
* @experimental
*/
export class DG3 {
+ /**
+ * Extract int from buffer
+ * @param data Buffer
+ * @param start Offset
+ * @param end Offset+length
+ */
private extractContent(data: Buffer, start: number, end: number): number {
if (end - start == 1) {
return data.subarray(start, end).readUInt8();
@@ -17,6 +23,10 @@ export class DG3 {
}
return parseInt(data.subarray(start,end).toString("hex"), 16)
}
+ /**
+ * Read Biometric data block
+ * @param tlv
+ */
readBDB(tlv: TLV): Interfaces.DecodedFingerprint {
if(parseInt(tlv.tag, 16) != 0x7f60) throw new Error(`Invalid object tag "0x${tlv.tag}", expected 0x7f60`);
let sbh = tlv.child[0]
diff --git a/src/index.ts b/src/index.ts
index e6dca6d..011cf8a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,5 +4,7 @@ export { DG2 } from "./dg2"
export { DG3 } from "./dg3"
export { DG5 } from "./dg5"
export { DG7 } from "./dg7"
+export { DG11 } from "./dg11"
+export { DG12 } from "./dg12"
export * as Enums from "./consts/enums"
export * as Interfaces from "./consts/interfaces"
\ No newline at end of file
diff --git a/tests/dgs/EF_DG11.bin b/tests/dgs/EF_DG11.bin
new file mode 100644
index 0000000000000000000000000000000000000000..73a6854e3767a079fae3693cb02b490027b6e266
GIT binary patch
literal 139
zcmd09?1&MM=ZhDJ*NzvAcZwH^7mgQ+7mXK-msk+b$KjHns^FEMk+*=CiIFj$pFKP?
zCnqy8H(r3*(8xGmn?K0sy-bA?E-9
literal 0
HcmV?d00001
diff --git a/tests/dgs/EF_DG12.bin b/tests/dgs/EF_DG12.bin
new file mode 100644
index 0000000000000000000000000000000000000000..011e90a772cc405a788d4c2094ea52f159c2fefd
GIT binary patch
literal 69
zcmd0Cj+bN!aSaZMS7T97VPssu%f!eSFU207nUj;5m>Vw*k(7Z@vWyJzazGr)4pJ**
M5E&oFY-nT*02#0jod5s;
literal 0
HcmV?d00001
diff --git a/tests/index.test.ts b/tests/index.test.ts
index dd96026..d27528a 100644
--- a/tests/index.test.ts
+++ b/tests/index.test.ts
@@ -1,6 +1,6 @@
import { test, expect } from "bun:test"
import { join } from "path"
-import { COM, DG1, DG2, DG3, DG5, DG7 } from "../src"
+import { COM, DG1, DG2, DG3, DG5, DG7, DG11, DG12 } from "../src"
const getDGContent = async (name: string): Promise => {
return Buffer.from(await Bun.file(join(import.meta.dir, "dgs", name)).bytes())
@@ -47,4 +47,34 @@ test("DG7", async () => {
expect(
new Bun.CryptoHasher("sha256").update(data[0]).digest().toString("hex")
).toBe("a3c5801a3692cf43495e391dc70d32e41526b4a5f700d5566c64688e897482e5")
+})
+
+test("DG11", async () => {
+ let data = DG11.load(await getDGContent("EF_DG11.bin"))
+ expect(data.nameOfHolder).toBe("Doe John")
+ expect(data.otherNames[0]).toBe("William")
+ expect(data.personalNumber).toBe("123")
+ expect(data.fullDateOfBirth).toBe(19700101)
+ expect(data.placeOfBirth[0]).toBe("TEST")
+ expect(data.placeOfBirth[1]).toBe("TEST")
+ expect(data.permanentAddress[0]).toBe("TEST")
+ expect(data.permanentAddress[1]).toBe("TEST")
+ expect(data.telephone).toBe("123")
+ expect(data.profession).toBe("TEST")
+ expect(data.title).toBe("TEST")
+ expect(data.personalSummary).toBe("TEST")
+ expect(data.otherValidTDNumbers[0]).toBe("123")
+ expect(data.otherValidTDNumbers[1]).toBe("123")
+ expect(data.custodyInformation).toBe("TEST")
+})
+
+test("DG12", async () => {
+ let data = DG12.load(await getDGContent("EF_DG12.bin"))
+ expect(data.dateOfIssue).toBe(20240101)
+ expect(data.issuingAuthority).toBe("TEST")
+ expect(data.namesOfOtherPersons[0]).toBe("William")
+ expect(data.endorsements).toBe("TEST")
+ expect(data.taxAndExitReqs).toBe("TEST")
+ expect(data.dateOfPersonalization).toBe(20240101123059)
+ expect(data.personalizationNumber).toBe("123")
})
\ No newline at end of file