Skip to content

Commit

Permalink
feat: v0.1.7 | Improve EAC schema, support for PA
Browse files Browse the repository at this point in the history
  • Loading branch information
li0ard committed Sep 25, 2024
1 parent 71e172f commit 74e8713
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 21 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
</a><br>
<b>tsemrtd</b><br>
<b>simple library for eMRTD datagroups</b>
<br>
<a href="https://tsemrtd.li0ard.rest">site</a>
&nbsp;•&nbsp;
<a href="https://tsemrtd.js.org">docs</a>
<br><br>
<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>
<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>
<a href="https://npmjs.com/package/@li0ard/tsemrtd"><img src="https://img.shields.io/npm/v/@li0ard/tsemrtd" /></a>
<a href="https://jsr.io/@li0ard/tsemrtd"><img src="https://jsr.io/badges/@li0ard/tsemrtd" /></a>
<br>
<hr>
</p>
Expand Down Expand Up @@ -78,8 +82,9 @@ await Bun.write("image.jp2",data[0].imageData)

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

## Acknowledgements
## Links

- [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)
- BSI TR-03110 - Specifications to EAC/PACE
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.6",
"version": "0.1.7",
"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
Expand Up @@ -2,7 +2,7 @@
"name": "@li0ard/tsemrtd",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"version": "0.1.6",
"version": "0.1.7",
"type": "module",
"author": "li0ard",
"repository": {
Expand Down
74 changes: 73 additions & 1 deletion src/asn1/eac.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AsnProp, AsnType, AsnPropTypes, AsnTypeTypes, AsnArray } from "@peculiar/asn1-schema";
import { AlgorithmIdentifier, SubjectPublicKeyInfo } from "@peculiar/asn1-x509";

/** Class for ASN1 schema of EAC/PACE Security info sequence */
export class SecurityInfo {
Expand All @@ -15,6 +16,77 @@ export class SecurityInfo {
optionalData?: any;
}

/** EF.CVCA file ID. Described by BSI TR-03110, section A.1.1.3 */
export class FileID {
/** File ID */
@AsnProp({ type: AsnPropTypes.OctetString })
fid: ArrayBuffer = new ArrayBuffer(0)

/** Short file ID */
@AsnProp({ type: AsnPropTypes.OctetString, optional: true })
sfid?: ArrayBuffer;
}

/** Information on an implementation of Terminal Authentication. Described by BSI TR-03110, section A.1.1.3 */
export class TerminalAuthenticationInfo {
/** OID of protocol */
@AsnProp({ type: AsnPropTypes.ObjectIdentifier })
protocol: string = "";

/** Terminal Authentication version */
@AsnProp({ type: AsnPropTypes.Integer })
version: number = 0; // MUST be 1 for TAv1 or 2 for TAv2

/** Indicate a (short) file identifier of the file EF.CVCA (v1 only) */
@AsnProp({ type: FileID, optional: true })
efCVCA?: FileID;
}

/** Information on an implementation of Chip Authentication. Described by BSI TR-03110, section A.1.1.2 */
export class ChipAuthenticationInfo {
/** OID of protocol */
@AsnProp({ type: AsnPropTypes.ObjectIdentifier })
protocol: string = "";

/** Chip Authentication version */
@AsnProp({ type: AsnPropTypes.Integer })
version: number = 0; // MUST be 1 for CAv1 or 2 for CAv2 or 3 for CAv3

/** Indicate the local key identifier (and domain parameters for key agreement) */
@AsnProp({ type: AsnPropTypes.Integer, optional: true })
keyId?: number;
}

/** A public key for Chip Authentication. Described by BSI TR-03110, section A.1.1.2 */
export class ChipAuthenticationPublicKeyInfo {
/** OID of protocol */
@AsnProp({ type: AsnPropTypes.ObjectIdentifier })
protocol: string = "";

/** Public key in encoded form ([RFC 5480](https://datatracker.ietf.org/doc/html/rfc5480#section-2)) */
@AsnProp({ type: SubjectPublicKeyInfo })
chipAuthenticationPublicKey: SubjectPublicKeyInfo = new SubjectPublicKeyInfo()

/** Indicate the local key identifier (and domain parameters for key agreement) */
@AsnProp({ type: AsnPropTypes.Integer, optional: true })
keyId?: number;
}

/** One set of domain parameters for Chip Authentication version 2 and version 3. Described by BSI TR-03110, section A.1.1.2 */
export class ChipAuthenticationDomainParameterInfo {
/** OID of protocol */
@AsnProp({ type: AsnPropTypes.ObjectIdentifier })
protocol: string = "";

/** Domain parameters */
@AsnProp({ type: AlgorithmIdentifier })
domainParameter: AlgorithmIdentifier = new AlgorithmIdentifier();

/** Indicate the local key identifier (and domain parameters for key agreement) */
@AsnProp({ type: AsnPropTypes.Integer, optional: true })
keyId?: number;
}

/** Class for ASN1 schema of SecurityInfo set. Described by ICAO 9303 p.10 section 4.7.14.2 */
@AsnType({ type: AsnTypeTypes.Set, itemType: SecurityInfo })
export class SecurityInfos extends AsnArray<SecurityInfo> {}
export class SecurityInfos extends AsnArray<SecurityInfo | TerminalAuthenticationInfo | ChipAuthenticationInfo | ChipAuthenticationPublicKeyInfo | ChipAuthenticationDomainParameterInfo> {}
3 changes: 3 additions & 0 deletions src/asn1/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * as EAC from "./eac"
export * as PKD from "./pkd"
export * as SOD from "./sod"
12 changes: 12 additions & 0 deletions src/consts/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type TLV from "node-tlv";
import type { EyeColor, FaceType, FingerImageType, FingerType, FingerprintImageType, Gender, HairColor, ImageColorSpace, ImageType, ImageUnit, IrisEyeSubtype, IrisImageFormat, SourceType } from "./enums";
import type { CertificateSet, SignerInfos } from "@peculiar/asn1-cms";
import type { LDSObject } from "../asn1/sod";

/** Template for BioAPI decoded datagroup */
interface AbstractBioTemplate {
Expand Down Expand Up @@ -171,4 +173,14 @@ export interface DecodedAdditionalDocumentData {
dateOfPersonalization: number,
/** Serial number of personalization system */
personalizationNumber: string
}

/** Decoded EF.SOD datagroup */
export interface DecodedSecurtyObjectOfDocument {
/** Included certificates (ex. Document Signer Certificate) */
certificates: CertificateSet,
/** LDS object with datagroup's hashes */
ldsObject: LDSObject,
/** SOD signatures */
signatures: SignerInfos
}
26 changes: 26 additions & 0 deletions src/consts/oids.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** BSI TR-03110. Terminal authentication */
export const TerminalAuthentication = "0.4.0.127.0.7.2.2.2"

/** BSI TR-03110. Chip authentication */
export enum ChipAuthDomainParameters {
CA_DH = "0.4.0.127.0.7.2.2.3.1",
CA_ECDH = "0.4.0.127.0.7.2.2.3.2"
}

/** BSI TR-03110. Chip authentication */
export enum ChipAuthPublicKey {
PK_DH = "0.4.0.127.0.7.2.2.1.1",
PK_ECDH = "0.4.0.127.0.7.2.2.1.2",
}

/** BSI TR-03110. Chip authentication */
export enum ChipAuthInfo {
CA_DH_3DES_CBC_CBC = "0.4.0.127.0.7.2.2.3.1.1",
CA_DH_AES_CBC_CMAC_128 = "0.4.0.127.0.7.2.2.3.1.2",
CA_DH_AES_CBC_CMAC_192 = "0.4.0.127.0.7.2.2.3.1.3",
CA_DH_AES_CBC_CMAC_256 = "0.4.0.127.0.7.2.2.3.1.4",
CA_ECDH_3DES_CBC_CBC = "0.4.0.127.0.7.2.2.3.2.1",
CA_ECDH_AES_CBC_CMAC_128 = "0.4.0.127.0.7.2.2.3.2.2",
CA_ECDH_AES_CBC_CMAC_192 = "0.4.0.127.0.7.2.2.3.2.3",
CA_ECDH_AES_CBC_CMAC_256 = "0.4.0.127.0.7.2.2.3.2.4",
}
28 changes: 24 additions & 4 deletions src/dg14.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import TLV from "node-tlv"
import { Enums } from "./index";
import { Enums, Oids } from "./index";
import { AsnConvert } from "@peculiar/asn1-schema";
import { SecurityInfos } from "./asn1/eac";
import { ChipAuthenticationDomainParameterInfo, ChipAuthenticationInfo, ChipAuthenticationPublicKeyInfo, SecurityInfos, TerminalAuthenticationInfo } from "./asn1/eac";

/**
* Class for working with DG14 (EAC/PACE authentication info)
Expand All @@ -13,8 +13,28 @@ export class DG14 {
*/
static load(data: string | Buffer): SecurityInfos {
let tlv = TLV.parse(data)
if(parseInt(tlv.tag, 16) != Enums.TAGS.DG14) throw new Error(`Invalid DG14 tag "0x${tlv.tag}", expected 0x${Enums.TAGS.DG14.toString(16)}`);
if(parseInt(tlv.tag, 16) != Enums.TAGS.DG14) throw new Error(`Invalid DG14 tag "0x${tlv.tag}", expected 0x${Enums.TAGS.DG14.toString(16)}`);

return AsnConvert.parse(tlv.bValue, SecurityInfos)
let infos = AsnConvert.parse(tlv.bValue, SecurityInfos)
let set = new SecurityInfos()
for(let i of infos) {
if(i.protocol == Oids.TerminalAuthentication) {
set.push(AsnConvert.parse(AsnConvert.serialize(i), TerminalAuthenticationInfo))
}
else if((Object.values(Oids.ChipAuthInfo) as string[]).includes(i.protocol)) {
set.push(AsnConvert.parse(AsnConvert.serialize(i), ChipAuthenticationInfo))
}
else if((Object.values(Oids.ChipAuthPublicKey) as string[]).includes(i.protocol)) {
set.push(AsnConvert.parse(AsnConvert.serialize(i), ChipAuthenticationPublicKeyInfo))
}
else if((Object.values(Oids.ChipAuthDomainParameters) as string[]).includes(i.protocol)) {
set.push(AsnConvert.parse(AsnConvert.serialize(i), ChipAuthenticationDomainParameterInfo))
}
else {
set.push(i)
}
}

return set
}
}
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ export { DG15 } from "./dg15"
export { SOD } from "./sod"
export { PKD } from "./pkd"
export * as Enums from "./consts/enums"
export * as Interfaces from "./consts/interfaces"
export * as Interfaces from "./consts/interfaces"
export * as Oids from "./consts/oids"
export * as Schemas from "./asn1/index"
12 changes: 8 additions & 4 deletions src/sod.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import TLV from "node-tlv"
import { Enums } from "./index";
import { Enums, Interfaces } from "./index";
import { AsnConvert, OctetString } from "@peculiar/asn1-schema";
import { LDSObject } from "./asn1/sod";
import { ContentInfo, SignedData } from "@peculiar/asn1-cms";
import { CertificateSet, ContentInfo, SignedData } from "@peculiar/asn1-cms";

/**
* Class for working with SOD (Security object)
Expand All @@ -12,13 +12,17 @@ export class SOD {
* Get version, algorithm, data groups hashes
* @param data Data of EF.SOD file
*/
static load(data: string | Buffer): LDSObject {
static load(data: string | Buffer): Interfaces.DecodedSecurtyObjectOfDocument {
let tlv = TLV.parse(data)
if(parseInt(tlv.tag, 16) != Enums.TAGS.SOD) throw new Error(`Invalid SOD tag "0x${tlv.tag}", expected 0x${Enums.TAGS.SOD.toString(16)}`);

let contentInfo = AsnConvert.parse(tlv.bValue, ContentInfo)
let signedData = AsnConvert.parse(contentInfo.content, SignedData)
let sod = AsnConvert.parse(signedData.encapContentInfo.eContent?.single as OctetString, LDSObject)
return sod
return {
certificates: signedData.certificates as CertificateSet,
ldsObject: sod,
signatures: signedData.signerInfos
}
}
}
22 changes: 17 additions & 5 deletions tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from "bun:test"
import { join } from "path"
import { COM, DG1, DG2, DG3, DG5, DG7, DG11, DG12, SOD, DG15, DG4 } from "../src"
import { COM, DG1, DG2, DG3, DG5, DG7, DG11, DG12, SOD, DG15, DG4, DG14, Schemas } from "../src"

const getDGContent = async (name: string): Promise<Buffer> => {
return Buffer.from(await Bun.file(join(import.meta.dir, "dgs", name)).bytes())
Expand Down Expand Up @@ -79,8 +79,18 @@ test("DG12", async () => {
expect(data.personalizationNumber).toBe("123")
})

test.todo("DG14", async () => {
test("DG14", async () => {
let data = DG14.load(await getDGContent("EF_DG14.bin"))
let ta = data.filter(i => i instanceof Schemas.EAC.TerminalAuthenticationInfo)[0] as Schemas.EAC.TerminalAuthenticationInfo
let ca = data.filter(i => i instanceof Schemas.EAC.ChipAuthenticationInfo)[0] as Schemas.EAC.ChipAuthenticationInfo
let caPk = data.filter(i => i instanceof Schemas.EAC.ChipAuthenticationPublicKeyInfo)[0] as Schemas.EAC.ChipAuthenticationPublicKeyInfo

expect(ta.protocol).toBe("0.4.0.127.0.7.2.2.2")
expect(ta.version).toBe(1)
expect(ca.protocol).toBe("0.4.0.127.0.7.2.2.3.2.1")
expect(ca.version).toBe(1)
expect(caPk.protocol).toBe("0.4.0.127.0.7.2.2.1.2")
expect(caPk.chipAuthenticationPublicKey.algorithm.algorithm).toBe("1.2.840.10045.2.1")
})

test("DG15", async () => {
Expand All @@ -91,7 +101,9 @@ test("DG15", async () => {

test("SOD", async () => {
let data = SOD.load(await getDGContent("EF_SOD.bin"))
expect(data.version).toBe(0)
expect(data.algorithm.algorithm).toBe("1.3.14.3.2.26")
expect(data.hashes.length).toBe(4)
expect(data.certificates.length).toBe(1)
expect(data.signatures.length).toBe(1)
expect(data.ldsObject.version).toBe(0)
expect(data.ldsObject.algorithm.algorithm).toBe("1.3.14.3.2.26")
expect(data.ldsObject.hashes.length).toBe(4)
})

0 comments on commit 74e8713

Please sign in to comment.