commit 0d726d07e7d8cf0226d2b5a74c24b6f9181493a4 Author: Damian Johnson atagar@torproject.org Date: Wed Oct 23 16:28:35 2019 -0700
Implement certificate encoding
There we go. Now that we parse certificates and extensions like datatypes we can implement their packing methods. --- stem/descriptor/certificate.py | 23 +++++++++++++++++++++++ test/unit/descriptor/certificate.py | 26 +++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/stem/descriptor/certificate.py b/stem/descriptor/certificate.py index dcf0c227..855f1262 100644 --- a/stem/descriptor/certificate.py +++ b/stem/descriptor/certificate.py @@ -79,6 +79,7 @@ import re import stem.descriptor.hidden_service import stem.descriptor.server_descriptor import stem.prereq +import stem.util import stem.util.enum import stem.util.str_tools
@@ -132,6 +133,14 @@ class Ed25519Extension(Field): if ext_type == ExtensionType.HAS_SIGNING_KEY and len(data) != 32: raise ValueError('Ed25519 HAS_SIGNING_KEY extension must be 32 bytes, but was %i.' % len(data))
+ def pack(self): + encoded = bytearray() + encoded += Size.SHORT.pack(len(self.data)) + encoded += Size.CHAR.pack(self.type) + encoded += Size.CHAR.pack(self.flag_int) + encoded += self.data + return bytes(encoded) + @staticmethod def pop(content): if len(content) < 4: @@ -265,6 +274,20 @@ class Ed25519CertificateV1(Ed25519Certificate): raise ValueError('Ed25519 certificate type %i is unrecognized' % self.type_int)
def to_base64(self, pem = False): + if self.encoded is None: + encoded = bytearray() + encoded += Size.CHAR.pack(self.version) + encoded += Size.CHAR.pack(self.type_int) + encoded += Size.LONG.pack(stem.util.datetime_to_unix(self.expiration) / 3600) + encoded += Size.CHAR.pack(self.key_type) + encoded += self.key + encoded += Size.CHAR.pack(len(self.extensions)) + + for extension in self.extensions: + encoded += extension.pack() + + self.encoded = '\n'.join(stem.util.str_tools._split_by_length(base64.b64encode(bytes(encoded + self.signature)), 64)) + if pem: return '-----BEGIN ED25519 CERT-----\n%s\n-----END ED25519 CERT-----' % self.encoded else: diff --git a/test/unit/descriptor/certificate.py b/test/unit/descriptor/certificate.py index dc7ff184..a5f7837a 100644 --- a/test/unit/descriptor/certificate.py +++ b/test/unit/descriptor/certificate.py @@ -12,7 +12,7 @@ import stem.util.str_tools import stem.prereq import test.require
-from stem.client.datatype import CertType +from stem.client.datatype import Size, CertType from stem.descriptor.certificate import ED25519_SIGNATURE_LENGTH, ExtensionType, Ed25519Certificate, Ed25519CertificateV1, Ed25519Extension from test.unit.descriptor import get_resource
@@ -93,6 +93,30 @@ class TestEd25519Certificate(unittest.TestCase): self.assertEqual([Ed25519Extension(4, 0, EXPECTED_EXTENSION_DATA)], cert.extensions) self.assertEqual(EXPECTED_SIGNATURE, cert.signature)
+ def test_extension_encoding(self): + """ + Pack an extension back into what we read. + """ + + extension = Ed25519Certificate.from_base64(ED25519_CERT).extensions[0] + expected = Size.SHORT.pack(len(EXPECTED_EXTENSION_DATA)) + Size.CHAR.pack(4) + Size.CHAR.pack(0) + EXPECTED_EXTENSION_DATA + + self.assertEqual(4, extension.type) + self.assertEqual(0, extension.flag_int) + self.assertEqual(EXPECTED_EXTENSION_DATA, extension.data) + self.assertEqual(expected, extension.pack()) + + def test_certificate_encoding(self): + """ + Pack a certificate back into what we read. + """ + + cert = Ed25519Certificate.from_base64(ED25519_CERT) + self.assertEqual(ED25519_CERT, cert.encoded) + + cert.encoded = None # clear cached encoding + self.assertEqual(ED25519_CERT, cert.to_base64()) + def test_non_base64(self): """ Parse data that isn't base64 encoded.