Skip to content

Commit

Permalink
feat: v0.1.4 | Add support for DG4 (Iris image)
Browse files Browse the repository at this point in the history
  • Loading branch information
li0ard committed Sep 6, 2024
1 parent 7b6f4ef commit b96270b
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 29 deletions.
61 changes: 44 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
<img src="https://raw.githubusercontent.com/li0ard/tsemrtd/main/.github/logo.svg" alt="tsemrtd logo" title="tsemrtd" width="120" /><br>
</a><br>
<b>tsemrtd</b><br>
<b>simple library for MRTD Datagroups</b>
<b>simple library for eMRTD Datagroups</b>
<br><br>
<img src="https://github.com/li0ard/tsemrtd/actions/workflows/test.yml/badge.svg" />
<img src="https://jsr.io/badges/@li0ard/tsemrtd" />
<a href="https://github.com/li0ard/tsemrtd/actions/workflows/test.yml"><img src="https://github.com/li0ard/tsemrtd/actions/workflows/test.yml/badge.svg" /></a>
<a href="https://jsr.io/@li0ard/tsemrtd"><img src="https://jsr.io/badges/@li0ard/tsemrtd" /></a>
<br>
<img src="https://img.shields.io/github/license/li0ard/tsemrtd" />
<a href="https://github.com/li0ard/tsemrtd/blob/main/LICENSE"><img src="https://img.shields.io/github/license/li0ard/tsemrtd" /></a>
<img src="https://img.shields.io/badge/-alpha-orange" />
<br>
<hr>
Expand All @@ -18,6 +18,13 @@
> tsemrtd is currently in alpha stage: the lib is not very stable yet, and there may be a lot of bugs
> feel free to try it out, though, any feedback is appreciated!
## Features

- Simple: Hides decoding process and provides simple and modern API
- Type-Safe: Most of the APIs are strictly typed to help your workflow
- Compliance: Fully complies with ICAO 9303 and ISO/IEC 19794 standards
- Supports Bun, Node.js, Deno, Browsers, Cloudflare Workers

## Installation

```bash
Expand All @@ -27,6 +34,7 @@ npx jsr add @li0ard/tsemrtd # for npm

## Usage

### Get MRZ
```ts
import { DG1 } from "@li0ard/tsemrtd"

Expand All @@ -37,18 +45,37 @@ console.log(data)
// C11T002JM4D<<9608122F1310317<<<<<<<<<<<<<<<6
```

### Extract and save photo
```ts
import { DG2 } from "@li0ard/tsemrtd"

let file = await Bun.file("EF_DG2.bin").bytes()
let data = DG2.load(Buffer.from(file))

await Bun.write("image.jp2",data[0].imageData)
```

## Supported DG's

- [x] COM
- [x] DG1
- [x] DG2
- [x] DG3
- [x] DG5
- [x] DG7
- [x] DG11
- [x] DG12
- [x] DG14
- [x] DG15
- [x] SOD

Library doesn't support datagroups #6,8,9,10,13,16 because they are defined for optional information for each state.
| Name | Descripion |
|------|------------------------------------------------------|
| COM | Manifest |
| DG1 | MRZ Info |
| DG2 | Face image |
| DG3 | Fingerprint image (Optional) |
| DG4 | Iris image (Optional) |
| DG5 | Displayed image (Optional) |
| DG7 | Signature image (Optional) |
| DG11 | Additional personal data (Optional) |
| DG12 | Additional document data (Optional) |
| DG14 | EAC/PACE data (Conditionally mandatory) |
| DG15 | Active authentication data (Conditionally mandatory) |
| SOD | Security object of document |

Library doesn't support datagroups #6,8,9,10,13,16 because they are defined for optional information for each state.

## Acknowledgements

- [jmrtd](https://jmrtd.org) - An Open Source Java Implementation of eMRTD
- [ICAO 9303](https://www.icao.int/publications/pages/publication.aspx?docnum=9303) - Specifications to MRTD
- ISO/IEC 19794(-4/-5/-6) - Specifications to Biometric Information Encoding (BioAPI)
2 changes: 1 addition & 1 deletion jsr.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@li0ard/tsemrtd",
"version": "0.1.3",
"version": "0.1.4",
"exports": "./src/index.ts",
"publish": {
"include": [
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "tsemrtd",
"module": "index.ts",
"version": "0.1.3",
"version": "0.1.4",
"type": "module",
"author": "li0ard",
"repository": {
Expand Down
41 changes: 38 additions & 3 deletions src/consts/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,25 @@ export enum FingerType {
LEFTLITTLE = 0x0A,
RIGHT4FINGERS = 0x0D,
LEFT4FINGER = 0x0E,
BOTHTHUMB = 0x0F
// to-do: add codes of names of palm areas
// table 6, section 8.3.3 iso/iec 19794-6-2006
BOTHTHUMB = 0x0F,

PALM_UNKNOWN = 20,
PALM_RIGHT_FULL = 21,
PALM_RIGHT_WRITER_S = 22,
PALM_LEFT_FULL = 23,
PALM_LEFT_WRITER_S = 24,
PALM_RIGHT_LOWER = 25,
PALM_RIGHT_UPPER = 26,
PALM_LEFT_LOWER = 27,
PALM_LEFT_UPPER = 28,
PALM_RIGHT_OTHER = 29,
PALM_LEFT_OTHER = 30,
PALM_RIGHT_INTERDIGITAL = 31,
PALM_RIGHT_THENAR = 32,
PALM_RIGHT_HYPOTHENAR = 33,
PALM_LEFT_INTERDIGITAL = 34,
PALM_LEFT_THENAR = 35,
PALM_LEFT_HYPOTHENAR = 36
}

/** ISO/IEC 19794-4. Type of fingerprint and palm image */
Expand All @@ -139,4 +155,23 @@ export enum FingerImageType {
export enum ImageUnit {
DPI = 0x01,
DPCM = 0x02
}

/** ISO/IEC 19794-6. Image format */
export enum IrisImageFormat {
RAWMONO = 0x02,
RAWRGB = 0x04,
JPEGMONO = 0x06,
JPEGRGB = 0x08,
JPEGLSMONO = 0x0A,
JPEGLSRGB = 0x0C,
JPEG2000MONO = 0x0E,
JPEG000RGB = 0x10
}

/** ISO/IEC 19794-6. Iris biometric subtype */
export enum IrisEyeSubtype {
UNDEFINED = 0x00,
LEFT = 0x01,
RIGHT = 0x02
}
33 changes: 31 additions & 2 deletions src/consts/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type TLV from "node-tlv";
import type { EyeColor, FaceType, FingerImageType, FingerType, FingerprintImageType, Gender, HairColor, ImageColorSpace, ImageType, ImageUnit, SourceType } from "./enums";
import type { EyeColor, FaceType, FingerImageType, FingerType, FingerprintImageType, Gender, HairColor, ImageColorSpace, ImageType, ImageUnit, IrisEyeSubtype, IrisImageFormat, SourceType } from "./enums";

/** Template for BioAPI decoded datagroup */
interface AbstractBioTemplate {
/** Standart Biometric Header. Described by ICAO 9303 p.10 section 4.7.2.1*/
sbh: TLV,
Expand Down Expand Up @@ -57,7 +58,9 @@ export interface DecodedImage extends AbstractBioTemplate {
/** Image source type */
sourceType: SourceType,
/** Image device type */
deviceType: number
deviceType: number,
/** Image Data Type */
imageType: ImageType,
}

/** Decoded EF.DG3 datagroup */
Expand Down Expand Up @@ -90,6 +93,32 @@ export interface DecodedFingerprint extends AbstractBioTemplate {
nrOfRepresention: number,
/** Name of finger/part of palm */
fingerType: FingerType
/** Image Data Type */
imageType: FingerprintImageType,
}

/** Decoded EF.DG4 datagroup */
export interface DecodedIris extends AbstractBioTemplate {
/** ID of Biometric scanner (by manufacturer) */
captureDeviceId: number,
/** Bit field of image properties. ISO/IEC 19794-6, table 2 */
imagePropertiesBits: number,
/** Iris diameter (in points) */
irisDiameter: number,
/** Bit depth of the grayscale scale */
depth: number,
/** Converting image to polar coordinate system */
imageTransformation: number,
/** ID of Biometric scanner (by issuing authority) */
deviceUniqueId: number,
/** Eye type */
biometricSubtype: IrisEyeSubtype,
/** Rotation angle of image */
rotationAngle: number,
/** Error of rotation angle */
rotationAngleUncertainty: number,
/** Image Data Type */
imageType: IrisImageFormat
}

/** Decoded EF.DG11 datagroup */
Expand Down
149 changes: 149 additions & 0 deletions src/dg4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import TLV from "node-tlv"
import { Enums, Interfaces } from "./index";

/**
* Class for working with DG4 (Iris)
* @experimental
*/
export class DG4 {
/**
* 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();
} else if (end - start < 4) {
return data.subarray(start, end).readInt16BE();
}
else if(end - start == 4) {
return data.subarray(start,end).readInt32BE()
}
return parseInt(data.subarray(start,end).toString("hex"), 16)
}
/**
* Read Biometric data block
* @param tlv
*/
readBDB(tlv: TLV): Interfaces.DecodedIris {
if(parseInt(tlv.tag, 16) != 0x7f60) throw new Error(`Invalid object tag "0x${tlv.tag}", expected 0x7f60`);
let sbh = tlv.child[0]
let firstBlock = tlv.child[1]
if(parseInt(firstBlock.tag, 16) != 0x5f2e && parseInt(firstBlock.tag, 16) != 0x7f2e) throw new Error(`Invalid object tag "0x${tlv.tag}", expected 0x5f2e or 0x7f2e`);
let data = firstBlock.bValue

if(data.subarray(0,4).readInt32BE() != 0x49495200) throw new Error("Biometric data block is invalid");
let offset = 4

if(this.extractContent(data, offset, offset+4) != 0x30313000) throw new Error("Version of Biometric data is not valid");
offset += 4;

let lengthOfRecord = this.extractContent(data, offset, offset + 4);
offset += 4;
//let dataLength = lengthOfRecord - 45;

let captureDeviceId = this.extractContent(data, offset, offset + 2);
offset += 2;

let count = this.extractContent(data, offset, offset + 1);
offset += 1;

if(count > 1) console.warn("[DG4] The record contains more than 1 image.")

let recordHeaderLength = this.extractContent(data, offset, offset + 2);
offset += 2;

if(recordHeaderLength != 45) throw new Error(`Expected header length 45, found ${recordHeaderLength}`);

let imagePropertiesBits = this.extractContent(data, offset, offset + 2);
offset += 2;

let irisDiameter = this.extractContent(data, offset, offset + 2);
offset += 2;

let imageType = this.extractContent(data, offset, offset + 2);
offset += 2;

let imageWidth = this.extractContent(data, offset, offset + 2);
offset += 2;

let imageHeight = this.extractContent(data, offset, offset + 2);
offset += 2;

let depth = this.extractContent(data, offset, offset + 1);
offset += 1;

let imageTransformation = this.extractContent(data, offset, offset + 1);
offset += 1;

let deviceUniqueId = this.extractContent(data, offset, offset + 16);
offset += 16;

let biometricSubtype = this.extractContent(data, offset, offset + 1);
offset += 1;

let biometricSubtypeCount = this.extractContent(data, offset, offset + 2);
offset += 2;

if(biometricSubtypeCount > 1) console.warn("[DG4] The record contains more than 1 image.")

let imageNumber = this.extractContent(data, offset, offset + 2);
offset += 2;

let quality = this.extractContent(data, offset, offset + 1);
offset += 1;

let rotationAngle = this.extractContent(data, offset, offset + 2);
offset += 2;

let rotationAngleUncertainty = this.extractContent(data, offset, offset + 2);
offset += 2;

let imageEnd = this.extractContent(data, offset, offset + 4) & 4294967295;
offset += 4;

let imageData = data.subarray(offset, offset + imageEnd)

return {
sbh,
lengthOfRecord,
captureDeviceId,
imagePropertiesBits,
irisDiameter,
imageType,
imageWidth,
imageHeight,
depth,
imageTransformation,
deviceUniqueId,
biometricSubtype,
quality,
rotationAngle,
rotationAngleUncertainty,
imageData
}
}
/**
* Get image of eye iris
* @param data Data of EF.DG4 file
*/
static load(data: string | Buffer): Interfaces.DecodedIris[] {
let tlv = TLV.parse(data)
if(parseInt(tlv.tag, 16) != Enums.TAGS.DG4) throw new Error(`Invalid DG4 tag "0x${tlv.tag}", expected 0x${Enums.TAGS.DG4.toString(16)}`);

let bigt = tlv.child[0]
if(parseInt(bigt.tag, 16) != 0x7f61) throw new Error(`Invalid object tag "0x${bigt.tag}", expected 0x7f61`);

let bict = bigt.child[0]
if(parseInt(bict.tag, 16) != 0x02) throw new Error(`Invalid object tag "0x${bict.tag}", expected 0x02`);

let bitCount = parseInt(bigt.child[0].value, 16)
let results = []
for(let i = 0; i < bitCount; i++) {
results.push(new DG4().readBDB(bigt.child[i + 1]))
}
return results
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { COM } from "./com"
export { DG1 } from "./dg1"
export { DG2 } from "./dg2"
export { DG3 } from "./dg3"
export { DG4 } from "./dg4"
export { DG5 } from "./dg5"
export { DG7 } from "./dg7"
export { DG11 } from "./dg11"
Expand Down
Binary file added tests/dgs/EF_DG4.bin
Binary file not shown.
Loading

0 comments on commit b96270b

Please sign in to comment.