[tor-commits] [stem/master] Stub certificate encoding/decoding methods

atagar at torproject.org atagar at torproject.org
Sun Nov 17 23:40:39 UTC 2019


commit 95f00b4fe19ac22abee90aec02498ffc6d56b76b
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Oct 20 17:11:57 2019 -0700

    Stub certificate encoding/decoding methods
    
    Finally settled on to_base64() and from_base64() methods. Not implemented yet,
    just stubbing the API I think we'll want.
---
 stem/descriptor/certificate.py       | 95 +++++++++++++++++++++++++++---------
 stem/descriptor/hidden_service.py    |  4 +-
 stem/descriptor/server_descriptor.py |  2 +-
 3 files changed, 76 insertions(+), 25 deletions(-)

diff --git a/stem/descriptor/certificate.py b/stem/descriptor/certificate.py
index 042687e3..ba2d1d55 100644
--- a/stem/descriptor/certificate.py
+++ b/stem/descriptor/certificate.py
@@ -19,9 +19,11 @@ used to for a variety of purposes...
   Ed25519Certificate - Ed25519 signing key certificate
     | +- Ed25519CertificateV1 - version 1 Ed25519 certificate
     |      |- is_expired - checks if certificate is presently expired
-    |      +- validate - validates signature of a server descriptor
+    |      |- signing_key - certificate signing key
+    |      +- validate - validates a descriptor's signature
     |
-    +- parse - reads base64 encoded certificate data
+    |- from_base64 - decodes base64 encoded certificate data
+    +- to_base64 - encodes base64 encoded certificate data
 
   Ed25519Extension - extension included within an Ed25519Certificate
 
@@ -123,12 +125,12 @@ class Ed25519Certificate(object):
   :var unicode encoded: base64 encoded ed25519 certificate
   """
 
-  def __init__(self, version, encoded):
+  def __init__(self, version):
     self.version = version
-    self.encoded = encoded
+    self.encoded = None  # TODO: remove in stem 2.x
 
   @staticmethod
-  def parse(content):
+  def from_base64(content):
     """
     Parses the given base64 encoded data as an Ed25519 certificate.
 
@@ -148,10 +150,23 @@ class Ed25519Certificate(object):
     version = stem.util.str_tools._to_int(Ed25519Certificate._b64_decode(content)[0:1])
 
     if version == 1:
-      return Ed25519CertificateV1(content)
+      return Ed25519CertificateV1.from_base64(content)
     else:
       raise ValueError('Ed25519 certificate is version %i. Parser presently only supports version 1.' % version)
 
+  def to_base64(self, pem = False):
+    """
+    Base64 encoded certificate data.
+
+    :param bool pem: include `PEM header/footer
+      <https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail>`_, for more
+      information see `RFC 7468 <https://tools.ietf.org/html/rfc7468>`_
+
+    :returns: **bytes** for our encoded certificate representation
+    """
+
+    raise NotImplementedError('Certificate encoding has not been implemented for %s' % type(self).__name__)
+
   @staticmethod
   def _from_descriptor(keyword, attribute):
     def _parse(descriptor, entries):
@@ -160,7 +175,7 @@ class Ed25519Certificate(object):
       if not block_contents or block_type != 'ED25519 CERT':
         raise ValueError("'%s' should be followed by a ED25519 CERT block, but was a %s" % (keyword, block_type))
 
-      setattr(descriptor, attribute, Ed25519Certificate.parse(block_contents))
+      setattr(descriptor, attribute, Ed25519Certificate.from_base64(block_contents))
 
     return _parse
 
@@ -177,7 +192,11 @@ class Ed25519Certificate(object):
       raise ValueError("Ed25519 certificate wasn't propoerly base64 encoded (%s):\n%s" % (exc, content))
 
   def __str__(self):
-    return '-----BEGIN ED25519 CERT-----\n%s\n-----END ED25519 CERT-----' % self.encoded
+    return self.to_base64(pem = True)
+
+  @staticmethod
+  def parse(content):
+    return Ed25519Certificate.from_base64(content)  # TODO: drop this alias in stem 2.x
 
 
 class Ed25519CertificateV1(Ed25519Certificate):
@@ -194,33 +213,60 @@ class Ed25519CertificateV1(Ed25519Certificate):
   :var bytes signature: certificate signature
   """
 
-  def __init__(self, content):
-    super(Ed25519CertificateV1, self).__init__(1, content)
+  def __init__(self, type_int, expiration, key_type, key, extensions, signature):
+    super(Ed25519CertificateV1, self).__init__(1)
+
+    self.type, self.type_int = ClientCertType.get(type_int)
+    self.expiration = expiration
+    self.key_type = key_type
+    self.key = key
+    self.extensions = extensions
+    self.signature = signature
+
+  def to_base64(self, pem = False):
+    if pem:
+      return '-----BEGIN ED25519 CERT-----\n%s\n-----END ED25519 CERT-----' % self.encoded
+    else:
+      return self.encoded
+
+  @staticmethod
+  def from_base64(content):
+    """
+    Parses the given base64 encoded data as a version 1 Ed25519 certificate.
+
+    :param str content: base64 encoded certificate
+
+    :returns: :class:`~stem.descriptor.certificate.Ed25519CertificateV1` for
+      this content
+
+    :raises: **ValueError** if certificate is malformed
+    """
+
     decoded = Ed25519Certificate._b64_decode(content)
 
     if len(decoded) < ED25519_HEADER_LENGTH + ED25519_SIGNATURE_LENGTH:
       raise ValueError('Ed25519 certificate was %i bytes, but should be at least %i' % (len(decoded), ED25519_HEADER_LENGTH + ED25519_SIGNATURE_LENGTH))
 
-    self.type, self.type_int = ClientCertType.get(stem.util.str_tools._to_int(decoded[1:2]))
+    type_enum, type_int = ClientCertType.get(stem.util.str_tools._to_int(decoded[1:2]))
 
-    if self.type in (ClientCertType.LINK, ClientCertType.IDENTITY, ClientCertType.AUTHENTICATE):
-      raise ValueError('Ed25519 certificate cannot have a type of %i. This is reserved for CERTS cells.' % self.type_int)
-    elif self.type == ClientCertType.ED25519_IDENTITY:
+    if type_enum in (ClientCertType.LINK, ClientCertType.IDENTITY, ClientCertType.AUTHENTICATE):
+      raise ValueError('Ed25519 certificate cannot have a type of %i. This is reserved for CERTS cells.' % type_int)
+    elif type_enum == ClientCertType.ED25519_IDENTITY:
       raise ValueError('Ed25519 certificate cannot have a type of 7. This is reserved for RSA identity cross-certification.')
-    elif self.type == ClientCertType.UNKNOWN:
-      raise ValueError('Ed25519 certificate type %i is unrecognized' % self.type_int)
+    elif type_enum == ClientCertType.UNKNOWN:
+      raise ValueError('Ed25519 certificate type %i is unrecognized' % type_int)
 
     # expiration time is in hours since epoch
     try:
-      self.expiration = datetime.datetime.utcfromtimestamp(stem.util.str_tools._to_int(decoded[2:6]) * 3600)
+      expiration = datetime.datetime.utcfromtimestamp(stem.util.str_tools._to_int(decoded[2:6]) * 3600)
     except ValueError as exc:
       raise ValueError('Invalid expiration timestamp (%s): %s' % (exc, stem.util.str_tools._to_int(decoded[2:6]) * 3600))
 
-    self.key_type = stem.util.str_tools._to_int(decoded[6:7])
-    self.key = decoded[7:39]
-    self.signature = decoded[-ED25519_SIGNATURE_LENGTH:]
+    key_type = stem.util.str_tools._to_int(decoded[6:7])
+    key = decoded[7:39]
+    signature = decoded[-ED25519_SIGNATURE_LENGTH:]
 
-    self.extensions = []
+    extensions = []
     extension_count = stem.util.str_tools._to_int(decoded[39:40])
     remaining_data = decoded[40:-ED25519_SIGNATURE_LENGTH]
 
@@ -248,12 +294,17 @@ class Ed25519CertificateV1(Ed25519Certificate):
       if extension_type == ExtensionType.HAS_SIGNING_KEY and len(extension_data) != 32:
         raise ValueError('Ed25519 HAS_SIGNING_KEY extension must be 32 bytes, but was %i.' % len(extension_data))
 
-      self.extensions.append(Ed25519Extension(extension_type, flags, extension_flags, extension_data))
+      extensions.append(Ed25519Extension(extension_type, flags, extension_flags, extension_data))
       remaining_data = remaining_data[4 + extension_length:]
 
     if remaining_data:
       raise ValueError('Ed25519 certificate had %i bytes of unused extension data' % len(remaining_data))
 
+    instance = Ed25519CertificateV1(type_int, expiration, key_type, key, extensions, signature)
+    instance.encoded = content
+
+    return instance
+
   def is_expired(self):
     """
     Checks if this certificate is presently expired or not.
diff --git a/stem/descriptor/hidden_service.py b/stem/descriptor/hidden_service.py
index c2673fb3..1819dfc5 100644
--- a/stem/descriptor/hidden_service.py
+++ b/stem/descriptor/hidden_service.py
@@ -508,7 +508,7 @@ def _parse_v3_introduction_points(descriptor, entries):
       onion_key = onion_key_line[5:] if onion_key_line.startswith('ntor ') else None
 
       _, block_type, auth_key_cert = entry['auth-key'][0]
-      auth_key_cert = Ed25519Certificate.parse(auth_key_cert)
+      auth_key_cert = Ed25519Certificate.from_base64(auth_key_cert)
 
       if block_type != 'ED25519 CERT':
         raise ValueError('Expected auth-key to have an ed25519 certificate, but was %s' % block_type)
@@ -517,7 +517,7 @@ def _parse_v3_introduction_points(descriptor, entries):
       enc_key = enc_key_line[5:] if enc_key_line.startswith('ntor ') else None
 
       _, block_type, enc_key_cert = entry['enc-key-cert'][0]
-      enc_key_cert = Ed25519Certificate.parse(enc_key_cert)
+      enc_key_cert = Ed25519Certificate.from_base64(enc_key_cert)
 
       if block_type != 'ED25519 CERT':
         raise ValueError('Expected enc-key-cert to have an ed25519 certificate, but was %s' % block_type)
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py
index a2be8483..9c29164b 100644
--- a/stem/descriptor/server_descriptor.py
+++ b/stem/descriptor/server_descriptor.py
@@ -406,7 +406,7 @@ def _parse_identity_ed25519_line(descriptor, entries):
   _parse_key_block('identity-ed25519', 'ed25519_certificate', 'ED25519 CERT')(descriptor, entries)
 
   if descriptor.ed25519_certificate:
-    descriptor.certificate = stem.descriptor.certificate.Ed25519Certificate.parse(descriptor.ed25519_certificate)
+    descriptor.certificate = stem.descriptor.certificate.Ed25519Certificate.from_base64(descriptor.ed25519_certificate)
 
 
 _parse_master_key_ed25519_line = _parse_simple_line('master-key-ed25519', 'ed25519_master_key')





More information about the tor-commits mailing list