Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #25

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Expand Up @@ -31,4 +31,4 @@ jobs:
- run: flutter pub get

- name: Flutter test
run: flutter test --no-sound-null-safety
run: flutter test
4 changes: 2 additions & 2 deletions example/android/app/build.gradle
Expand Up @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 32
compileSdkVersion 33

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
Expand All @@ -40,7 +40,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.mrtdeg"
minSdkVersion 21
targetSdkVersion 32
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Expand Down
5 changes: 3 additions & 2 deletions example/android/build.gradle
@@ -1,5 +1,6 @@
buildscript {
ext.kotlin_version = '1.6.10'
//ext.kotlin_version = '1.6.21'
ext.kotlin_version = '1.8.0'
repositories {
google()
jcenter()
Expand All @@ -26,6 +27,6 @@ subprojects {
project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
2 changes: 1 addition & 1 deletion example/lib/main.dart
Expand Up @@ -560,7 +560,7 @@ class _MrtdHomePageState extends State<MrtdHomePage> {
SizedBox(height: 40),
_buildForm(context),
SizedBox(height: 20),
PlatformButton( // btn Read MRTD
PlatformElevatedButton( // btn Read MRTD
onPressed: _disabledInput() || !_mrzData.currentState!.validate() ? null : _readMRTD,
child: PlatformText(_isReading ? 'Reading ...' : 'Read Passport'),
),
Expand Down
6 changes: 3 additions & 3 deletions example/pubspec.yaml
Expand Up @@ -15,7 +15,7 @@ description: A new Flutter project.
version: 1.2.1

environment:
sdk: '>=2.17.1 <3.0.0'
sdk: '>=2.17.6 <3.0.0'

publish_to: none

Expand All @@ -26,8 +26,8 @@ dependencies:
sdk: flutter
flutter_localizations:
sdk: flutter
flutter_platform_widgets: ^1.7.1
intl: ^0.17.0
flutter_platform_widgets: ^3.3.5
intl: ^0.18.0

# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
Expand Down
Empty file added lib/src/constants/asn1_ber.dart
Empty file.
15 changes: 11 additions & 4 deletions lib/src/extension/string_apis.dart
Expand Up @@ -16,7 +16,7 @@ extension StringDecodeApis on String {

extension StringYYMMDDateApi on String {
DateTime parseDateYYMMDD() {
if(length < 6) {
if (length < 6) {
throw FormatException("invalid length of compact date string");
}

Expand All @@ -27,10 +27,17 @@ extension StringYYMMDDateApi on String {
// Sub 100 years from parsed year if greater than 10 years and 5 months from now.
final now = DateTime.now();
final tenYearsFromNow = now.year + 10;
if (y > tenYearsFromNow ||
(y == tenYearsFromNow && now.month + 5 < m)) {
if (y > tenYearsFromNow || (y == tenYearsFromNow && now.month + 5 < m)) {
y -= 100;
}
return DateTime(y, m, d);
}
}

DateTime parseDate() {
if (length == 6) {
return this.parseDateYYMMDD();
} else {
return DateTime.parse(this);
}
}
}
103 changes: 103 additions & 0 deletions lib/src/lds/asn1ObjectIdentifiers.dart
@@ -0,0 +1,103 @@
// Created by Nejc Skerjanc, copyright © 2023 ZeroPass. All rights reserved.

import 'dart:typed_data';
import 'package:dmrtd/extensions.dart';
import 'package:logging/logging.dart';
import 'package:pointycastle/asn1.dart';
import 'package:pointycastle/asn1/object_identifiers_database.dart';

import '../../types/exception.dart';


// here you can add additional object identifiers that are not defined in pointycastle library
List<Map<String, Object>> customOIDS = [
{
'identifierString': '1.2.3.4.5', 'readableName': 'someName', 'identifier': [0, 1, 2, 3, 4, 5]
}
];


// add additional object identifiers

class ASN1ObjectIdentifierException implements DMRTDException {
final String message;
@override
String exceptionName = 'ASN1ObjectIdentifierException';

ASN1ObjectIdentifierException(this.message);
//@override
//String toString() {
// String result = 'ASN1ObjectIdentifierException';
// if (message is String) return '$result: $message';
// return result;
//}


}


class ASN1ObjectIdentifiers {
// object identifiers that are defined in pointycastle library
List<Map<String, Object>> _OIDS = oi;
final _log = Logger("ASN1ObjectIdentifiers");


ASN1ObjectIdentifiers(){
// add custom object identifiers to existing ones
for (var customOID in customOIDS) {
if (!checkOID(item:customOID)){
throw ASN1ObjectIdentifierException('Object identifier is not valid.');
}
_OIDS.add(customOID);
}
}

// check if object identifier is valid
bool checkOID({required Map<String, Object> item}){
//check if list contains all required keys
if (!item.containsKey('identifier') || !item.containsKey('identifierString') || !item.containsKey('readableName')) {
_log.error('Object identifier must contain identifier, identifierString and readableName.');
return false;
}

if (item['identifier'] is! List<int>) {
_log.error('Object identifier identifier must be List<int>.');
return false;
}
if (item['identifierString'] is! String) {
_log.error('Object identifier identifierString must be String.');
return false;
}
if (item['readableName'] is! String) {
_log.error('Object identifier readableName must be String.');
return false;
}
return true;
}


// has object identifier with identifier string
bool hasOIDWithIdentifierString(String identifierString) {
return _OIDS.any((element) => element['identifierString'] == identifierString);
}


// get object identifier by identifier string
Map<String, Object> getOIDByIdentifierString(String identifierString) {
return _OIDS.firstWhere((element) => element['identifierString'] == identifierString, orElse: () =>
throw ASN1ObjectIdentifierException('Object identifier with identifier string $identifierString does not exist.'));
}

// has object identifier wih identifier
bool hasOIDWithIdentifier(List<int> identifier) {
return _OIDS.any((element) => element['identifier'] == identifier);
}

// get object identifier by identifier
Map<String, Object> getOIDByIdentifier(List<int> identifier) {
return _OIDS.firstWhere((element) => element['identifier'] == identifier, orElse: () =>
throw ASN1ObjectIdentifierException('Object identifier with identifier $identifier does not exist.'));
}

}

126 changes: 125 additions & 1 deletion lib/src/lds/df1/efdg11.dart
@@ -1,14 +1,73 @@
// Created by Crt Vavros, copyright © 2022 ZeroPass. All rights reserved.
// ignore_for_file: constant_identifier_names

import 'dart:convert';
import 'dart:core';
import 'dart:typed_data';
import 'package:dmrtd/dmrtd.dart';
import 'package:dmrtd/extensions.dart';

import 'dg.dart';

class EfDG11 extends DataGroup {
static const FID = 0x010B;
static const SFI = 0x0B;
static const TAG = DgTag(0x6B);

static const FULL_NAME_TAG = 0x5F0E;
static const OTHER_NAME_TAG = 0x5F0F;
static const PERSONAL_NUMBER_TAG = 0x5F10;

// In 'CCYYMMDD' format.
static const FULL_DATE_OF_BIRTH_TAG = 0x5F2B;

// Fields separated by '<'
static const PLACE_OF_BIRTH_TAG = 0x5F11;

// Fields separated by '<'
static const PERMANENT_ADDRESS_TAG = 0x5F42;
static const TELEPHONE_TAG = 0x5F12;
static const PROFESSION_TAG = 0x5F13;
static const TITLE_TAG = 0x5F14;
static const PERSONAL_SUMMARY_TAG = 0x5F15;

// Compressed image per ISO/IEC 10918
static const PROOF_OF_CITIZENSHIP_TAG = 0x5F16;

// Separated by '<'
static const OTHER_VALID_TD_NUMBERS_TAG = 0x5F17;
static const CUSTODY_INFORMATION_TAG = 0x5F18;

static const TAG_LIST_TAG = 0x5c;

String? _nameOfHolder;
final _otherNames = <String>[];
String? _personalNumber;
DateTime? _fullDateOfBirth;
final _placeOfBirth = <String>[];
final _permanentAddress = <String>[];
String? _telephone;
String? _profession;
String? _title;
String? _personalSummary;
Uint8List? _proofOfCitizenship;
var _otherValidTDNumbers = <String>[];
String? _custodyInformation;

String? get nameOfHolder => _nameOfHolder;
List<String> get otherNames => _otherNames;
String? get personalNumber => _personalNumber;
DateTime? get fullDateOfBirth => _fullDateOfBirth;
List<String> get placeOfBirth => _placeOfBirth;
List<String> get permanentAddress => _permanentAddress;
String? get telephone => _telephone;
String? get profession => _profession;
String? get title => _title;
String? get ersonalSummary => _personalSummary;
Uint8List? get proofOfCitizenship => _proofOfCitizenship;
List<String> get otherValidTDNumbers => _otherValidTDNumbers;
String? get custodyInformation => _custodyInformation;

EfDG11.fromBytes(Uint8List data) : super.fromBytes(data);

@override
Expand All @@ -19,4 +78,69 @@ class EfDG11 extends DataGroup {

@override
int get tag => TAG.value;
}

@override
void parse(Uint8List content) {
final tlv = TLV.fromBytes(content);
if (tlv.tag != tag) {
throw EfParseError(
"Invalid DG11 tag=${tlv.tag.hex()}, expected tag=${TAG.value.hex()}");
}

final data = tlv.value;
final tagListTag = TLV.decode(data);
if (tagListTag.tag.value != TAG_LIST_TAG) {
throw EfParseError(
"Invalid version object tag=${tagListTag.tag.value.hex()}, expected version object with tag=5c");
}
var tagListLength = tlv.value.length;
int tagListBytesRead = tagListTag.encodedLen;

while (tagListBytesRead < tagListLength) {
final uvtv = TLV.decode(data.sublist(tagListBytesRead));
tagListBytesRead += uvtv.encodedLen;

switch (uvtv.tag.value) {
case FULL_NAME_TAG:
_nameOfHolder = utf8.decode(uvtv.value);
break;
case PERSONAL_NUMBER_TAG:
_personalNumber = utf8.decode(uvtv.value);
break;
case OTHER_NAME_TAG:
_otherNames.add(utf8.decode(uvtv.value));
break;
case FULL_DATE_OF_BIRTH_TAG:
_fullDateOfBirth = String.fromCharCodes(uvtv.value).parseDate();
break;
case PLACE_OF_BIRTH_TAG:
_placeOfBirth.add(utf8.decode(uvtv.value));
break;
case PERMANENT_ADDRESS_TAG:
_permanentAddress.add(utf8.decode(uvtv.value));
break;
case TELEPHONE_TAG:
_telephone = utf8.decode(uvtv.value);
break;
case PROFESSION_TAG:
_profession = utf8.decode(uvtv.value);
break;
case TITLE_TAG:
_title = utf8.decode(uvtv.value);
break;
case PERSONAL_SUMMARY_TAG:
_personalSummary = utf8.decode(uvtv.value);
break;
case PROOF_OF_CITIZENSHIP_TAG:
_proofOfCitizenship = uvtv.value;
break;
case OTHER_VALID_TD_NUMBERS_TAG:
_otherValidTDNumbers.add(utf8.decode(uvtv.value));
break;
case CUSTODY_INFORMATION_TAG:
_custodyInformation = utf8.decode(uvtv.value);
break;
}
}
}
}