From 74e87131070a820190fb6376f0c5b41f82be20ff Mon Sep 17 00:00:00 2001
From: Lizard
Date: Wed, 25 Sep 2024 19:21:49 +0600
Subject: [PATCH] feat: v0.1.7 | Improve EAC schema, support for PA
---
README.md | 13 ++++---
jsr.json | 2 +-
package.json | 2 +-
src/asn1/eac.ts | 74 +++++++++++++++++++++++++++++++++++++++-
src/asn1/index.ts | 3 ++
src/consts/interfaces.ts | 12 +++++++
src/consts/oids.ts | 26 ++++++++++++++
src/dg14.ts | 28 ++++++++++++---
src/index.ts | 4 ++-
src/sod.ts | 12 ++++---
tests/index.test.ts | 22 +++++++++---
11 files changed, 177 insertions(+), 21 deletions(-)
create mode 100644 src/asn1/index.ts
create mode 100644 src/consts/oids.ts
diff --git a/README.md b/README.md
index 93e237b..52573fe 100644
--- a/README.md
+++ b/README.md
@@ -4,12 +4,16 @@
tsemrtd
simple library for eMRTD datagroups
+
+ site
+ •
+ docs
-
-
-
+
+
+
@@ -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
\ No newline at end of file
diff --git a/jsr.json b/jsr.json
index 1548409..f0dc52f 100644
--- a/jsr.json
+++ b/jsr.json
@@ -1,6 +1,6 @@
{
"name": "@li0ard/tsemrtd",
- "version": "0.1.6",
+ "version": "0.1.7",
"exports": "./src/index.ts",
"publish": {
"include": [
diff --git a/package.json b/package.json
index 4b585e0..84a67db 100644
--- a/package.json
+++ b/package.json
@@ -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": {
diff --git a/src/asn1/eac.ts b/src/asn1/eac.ts
index 6fcf548..1cb6a30 100644
--- a/src/asn1/eac.ts
+++ b/src/asn1/eac.ts
@@ -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 {
@@ -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 {}
\ No newline at end of file
+export class SecurityInfos extends AsnArray {}
\ No newline at end of file
diff --git a/src/asn1/index.ts b/src/asn1/index.ts
new file mode 100644
index 0000000..bb8af07
--- /dev/null
+++ b/src/asn1/index.ts
@@ -0,0 +1,3 @@
+export * as EAC from "./eac"
+export * as PKD from "./pkd"
+export * as SOD from "./sod"
\ No newline at end of file
diff --git a/src/consts/interfaces.ts b/src/consts/interfaces.ts
index 95ec24d..8a55ccf 100644
--- a/src/consts/interfaces.ts
+++ b/src/consts/interfaces.ts
@@ -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 {
@@ -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
}
\ No newline at end of file
diff --git a/src/consts/oids.ts b/src/consts/oids.ts
new file mode 100644
index 0000000..3b04243
--- /dev/null
+++ b/src/consts/oids.ts
@@ -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",
+}
\ No newline at end of file
diff --git a/src/dg14.ts b/src/dg14.ts
index 5b0ce51..a02d902 100644
--- a/src/dg14.ts
+++ b/src/dg14.ts
@@ -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)
@@ -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
}
}
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index 9d8295d..99a4d6c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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"
\ No newline at end of file
+export * as Interfaces from "./consts/interfaces"
+export * as Oids from "./consts/oids"
+export * as Schemas from "./asn1/index"
\ No newline at end of file
diff --git a/src/sod.ts b/src/sod.ts
index 94a24dc..04aafe8 100644
--- a/src/sod.ts
+++ b/src/sod.ts
@@ -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)
@@ -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
+ }
}
}
\ No newline at end of file
diff --git a/tests/index.test.ts b/tests/index.test.ts
index 23d7f95..9e5572b 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, 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 => {
return Buffer.from(await Bun.file(join(import.meta.dir, "dgs", name)).bytes())
@@ -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 () => {
@@ -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)
})
\ No newline at end of file