Skip to content

Commit

Permalink
Allow better pickle integration (#240)
Browse files Browse the repository at this point in the history
The way asn1crypto manages its classes does not always play nice with
unpickling objects. In particular, pickled Asn1Values cannot always be
unpickled in a "fresh" interpreter where the class setup methods for
asn1crypto's classes haven't been called yet. See discussion in #239.

This commit implements __reduce__ on Asn1Value in such a way that pickle
will use asn1crypto's own DER serialisation/deserialisation
functionality to handle pickling and unpickling.

Fixes #239.
  • Loading branch information
MatthiasValvekens authored Oct 18, 2022
1 parent e116afb commit fad689f
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 0 deletions.
15 changes: 15 additions & 0 deletions asn1crypto/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,15 @@ def load(encoded_data, strict=False):
return Asn1Value.load(encoded_data, strict=strict)


def unpickle_helper(asn1crypto_cls, der_bytes):
"""
Helper function to integrate with pickle.
Note that this must be an importable top-level function.
"""
return asn1crypto_cls.load(der_bytes)


class Asn1Value(object):
"""
The basis of all ASN.1 values
Expand Down Expand Up @@ -481,6 +490,12 @@ def __unicode__(self):

return self.__repr__()

def __reduce__(self):
"""
Permits pickling Asn1Value objects using their DER representation.
"""
return unpickle_helper, (self.__class__, self.dump())

def _new_instance(self):
"""
Constructs a new copy of the current object, preserving any tagging
Expand Down
9 changes: 9 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function

import pickle
import unittest
import os
from datetime import datetime, timedelta
Expand Down Expand Up @@ -1375,3 +1376,11 @@ def test_broken_object_identifier(self):

with self.assertRaisesRegex(ValueError, "Second arc must be "):
core.ObjectIdentifier("0.40")

def test_pickle_integration(self):
orig = Seq({'id': '2.3.4', 'value': b"\xde\xad\xbe\xef"})
pickled_bytes = pickle.dumps(orig)
# ensure that our custom pickling implementation was used
self.assertIn(b"unpickle_helper", pickled_bytes)
unpickled = pickle.loads(pickled_bytes)
self.assertEqual(orig.native, unpickled.native)

0 comments on commit fad689f

Please sign in to comment.