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

Add dg2, dg7 and dg11 #3

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
python_requires=">=3.9.11",
install_requires=[
'asn1crypto>=1.5.1',
'cryptography>=41.0.3'
'cryptography==41.0.3'
],
extras_require={
'tests': [
Expand Down
51 changes: 23 additions & 28 deletions src/pymrtd/ef/__init__.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
from .base import (
ElementaryFile,
ElementaryFileError,
LDSVersionInfo
)

from .dg import (
DataGroup,
DataGroupNumber,
DG1,
DG14,
DG15
)

from .mrz import (
MachineReadableZone
)

from .sod import (
SOD,
SODError
)
from .base import ElementaryFile, ElementaryFileError, LDSVersionInfo
from .dg import DataGroup, DataGroupNumber
from .dg1 import DG1, DataGroup1
from .dg2 import DG2, DataGroup2
from .dg7 import DG7, DataGroup7
from .dg11 import DG11, DataGroup11
from .dg14 import DG14
from .dg15 import DG15
from .errors import NFCPassportReaderError
from .sod import SOD, SODError

Comment on lines +1 to +10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please order imports in list to match the original style.

__all__ = [
"ElementaryFile",
"ElementaryFileError",
"LDSVersionInfo",
"DataGroup",
"DataGroupNumber",
"DataGroup1",
"DG1",
"DataGroup2",
"DG2",
"DataGroup7",
"DG7",
"DataGroup11",
"DG11",
"DG14",
"DG15",
"ElementaryFile",
"ElementaryFileError",
"LDSVersionInfo",
"MachineReadableZone",
"NFCPassportReaderError",
"SOD",
"SODError"
]
"SODError",
]
106 changes: 73 additions & 33 deletions src/pymrtd/ef/base.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,68 @@
import hashlib

import asn1crypto.core as asn1
import asn1crypto.parser as asn1Parser


class LDSVersionInfo(asn1.Sequence):
_fields = [
('ldsVersion', asn1.PrintableString),
('unicodeVersion', asn1.PrintableString),
("ldsVersion", asn1.PrintableString),
("unicodeVersion", asn1.PrintableString),
Comment on lines +9 to +10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, Use single quote mark.

]


class ElementaryFileError(ValueError):
pass


class ElementaryFile(asn1.Asn1Value):
_content_spec = None
_str_rep = None

def __init__(self, explicit=None, implicit=None, no_explicit=False, tag_type=None, class_=None, tag=None,
optional=None, default=None, contents=None, method=None, spec=None):
def __init__(
self,
explicit=None,
implicit=None,
no_explicit=False,
tag_type=None,
class_=None,
tag=None,
optional=None,
default=None,
contents=None,
method=None,
spec=None,
):
if spec:
self._content_spec = spec
super().__init__(explicit=explicit, implicit=implicit, no_explicit=no_explicit, tag_type=tag_type, class_=class_, tag=tag,
optional=optional, default=default, contents=contents, method=method)
super().__init__(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, add 1 line break.

explicit=explicit,
implicit=implicit,
no_explicit=no_explicit,
tag_type=tag_type,
class_=class_,
tag=tag,
optional=optional,
default=default,
contents=contents,
method=method,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, add 1 line break before next self._content.

self._content = None
self._fp = None
self._fp = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, align assignment to match original style.


def __str__(self):
"""
Returns string representation of self i.e. EF(fp=XXXXXXXXXXXXXXXX)
"""
if self._str_rep is None:
self._str_rep = f'EF(fp={self.fingerprint})'
self._str_rep = f"EF(fp={self.fingerprint})"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, Use single quote mark.

return self._str_rep

@classmethod
def load(cls, encoded_data: bytes, strict=False): #pylint: disable=arguments-differ
'''
def load(
cls, encoded_data: bytes, strict=False
): # pylint: disable=arguments-differ
"""
Loads a BER/DER-encoded byte string using the current class as the spec
:param encoded_data:
A byte string of BER or DER encoded data
Expand All @@ -44,29 +71,35 @@ def load(cls, encoded_data: bytes, strict=False): #pylint: disable=arguments-dif
ValueError will be raised when trailing data exists
:return:
A instance of the current class
'''
"""

class_, method, tag, header, contents, trailer = asn1Parser.parse(encoded_data, strict=strict) #pylint: disable=unused-variable
class_, method, tag, header, contents, trailer = asn1Parser.parse(
encoded_data, strict=strict
) # pylint: disable=unused-variable
value = cls(class_=class_, tag=tag, method=method, contents=contents)

if cls.class_ is not None and value.class_ != cls.class_:
raise ElementaryFileError("Invalid elementary file class, expected class '{}' got '{}'"
.format(
raise ElementaryFileError(
"Invalid elementary file class, expected class '{}' got '{}'".format(
asn1.CLASS_NUM_TO_NAME_MAP.get(cls.class_, cls.class_),
asn1.CLASS_NUM_TO_NAME_MAP.get(value.class_, value.class_)
))
asn1.CLASS_NUM_TO_NAME_MAP.get(value.class_, value.class_),
)
)
if cls.method is not None and value.method != cls.method:
raise ElementaryFileError("Invalid elementary file method , expected method '{}' got '{}'"
.format(
raise ElementaryFileError(
"Invalid elementary file method , expected method '{}' got '{}'".format(
asn1.METHOD_NUM_TO_NAME_MAP.get(cls.method, cls.method),
asn1.METHOD_NUM_TO_NAME_MAP.get(value.method, value.method)
))
asn1.METHOD_NUM_TO_NAME_MAP.get(value.method, value.method),
)
)

if cls.tag is not None and value.tag != cls.tag:
raise ElementaryFileError(f"Invalid elementary file tag, expected tag '{cls.tag}' got '{value.tag}'")
raise ElementaryFileError(
f"Invalid elementary file tag, expected tag '{cls.tag}' got '{value.tag}'"
)

# Force parsing of content. This is done in order for any invalid content to raise an exception
value.content #pylint: disable=pointless-statement
value.content # pylint: disable=pointless-statement
return value

@property
Expand All @@ -76,23 +109,23 @@ def fingerprint(self) -> str:
"""
if self._fp is None:
d = hashlib.sha256(self.dump()).digest()
self._fp = d[0:8].hex().upper().rjust(16, '0')
self._fp = d[0:8].hex().upper().rjust(16, "0")
return self._fp

@property
def content(self):
''' Returns content object of a type content_type '''
"""Returns content object of a type content_type"""
if self._content is None:
self._parse_content()
return self._content

@property
def native(self):
'''
"""
The native Python data type representation of this value
:return:
A native representation of content object or None.
'''
"""

if self.contents is None:
return None
Expand All @@ -102,28 +135,35 @@ def native(self):
return self.content.native

def _parse_content(self):
'''
"""
Parses the contents and generates Asn1Value content objects based on the
definitions from _content_spec.
:raises:
ValueError - when an error occurs parsing content object
'''
"""

self._content = None
if self.contents is None:
return

if self._content_spec is not None:
if not issubclass(self._content_spec, asn1.Asn1Value):
raise ValueError(f'_content_spec must be of a Ans1Value type, not {self._content_spec!r}')
raise ValueError(
f"_content_spec must be of a Ans1Value type, not {self._content_spec!r}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, Use single quote mark.

)

try:
self._content = self._content_spec.load(self.contents, strict=True)
if isinstance(self._content, (asn1.Sequence, asn1.SequenceOf)):
self._content._parse_children(recurse=True) #pylint: disable=protected-access
self._content._parse_children(
recurse=True
) # pylint: disable=protected-access
except (ValueError, TypeError) as e:
from asn1crypto._types import type_name #pylint: disable=import-outside-toplevel
from asn1crypto._types import (
type_name,
) # pylint: disable=import-outside-toplevel

self._content = None
args = e.args[1:]
e.args = (e.args[0] + f'\n while parsing {type_name(self)}',) + args
args = e.args[1:]
e.args = (e.args[0] + f"\n while parsing {type_name(self)}",) + args
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, Use single quote mark.

raise
Loading
Loading