[tor-commits] [stem/master] Implement ed25519 certificate encoding.

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


commit c723cb5c3eba172b414b61c8e9560ddc89b39303
Author: George Kadianakis <desnacked at riseup.net>
Date:   Thu Oct 10 20:43:19 2019 +0300

    Implement ed25519 certificate encoding.
    
    I had trouble adapting the certificate.py code to do encoding, since it seems
    like the Ed25519CertificateV1 class has been made with parsing in mind, but in
    this case I will need to provide its raw attributes (keys, etc.)  and have it
    encode them into an actual certificate.
    
    I made my own class that is meant for encoding but we should likely merge these
    into one!
---
 stem/descriptor/certificate.py | 150 ++++++++++++++++++++++++++++++++++-------
 1 file changed, 126 insertions(+), 24 deletions(-)

diff --git a/stem/descriptor/certificate.py b/stem/descriptor/certificate.py
index 6ba92ee7..ac6d455f 100644
--- a/stem/descriptor/certificate.py
+++ b/stem/descriptor/certificate.py
@@ -78,19 +78,19 @@ import stem.prereq
 import stem.descriptor.server_descriptor
 import stem.util.enum
 import stem.util.str_tools
+import stem.util
+
+from cryptography.hazmat.primitives import serialization
 
 ED25519_HEADER_LENGTH = 40
 ED25519_SIGNATURE_LENGTH = 64
 ED25519_ROUTER_SIGNATURE_PREFIX = b'Tor router descriptor signature v1'
 
 CertType = stem.util.enum.UppercaseEnum(
-  'SIGNING',
-  'LINK_CERT',
-  'AUTH',
-  'HS_V3_DESC_SIGNING',
-  'HS_V3_INTRO_AUTH',
-  'HS_V3_INTRO_ENCRYPT',
-)
+  'RESERVED_0', 'RESERVED_1', 'RESERVED_2', 'RESERVED_3',
+  'SIGNING', 'LINK_CERT', 'AUTH', 'RESERVED_RSA',
+  'HS_V3_DESC_SIGNING', 'HS_V3_INTRO_AUTH', 'RESERVED_0A',
+  'HS_V3_INTRO_ENC', 'HS_V3_NTOR_ENC')
 
 ExtensionType = stem.util.enum.Enum(('HAS_SIGNING_KEY', 4),)
 ExtensionFlag = stem.util.enum.UppercaseEnum('AFFECTS_VALIDATION', 'UNKNOWN')
@@ -157,6 +157,7 @@ class Ed25519Certificate(object):
     def _parse(descriptor, entries):
       value, block_type, block_contents = entries[keyword][0]
 
+      # XXX ATAGAR This ValueError never actually surfaces if it triggers...
       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))
 
@@ -188,26 +189,16 @@ class Ed25519CertificateV1(Ed25519Certificate):
       raise ValueError('Ed25519 certificate was %i bytes, but should be at least %i' % (len(decoded), ED25519_HEADER_LENGTH + ED25519_SIGNATURE_LENGTH))
 
     cert_type = stem.util.str_tools._to_int(decoded[1:2])
+    try:
+      self.type = CertType.keys()[cert_type]
+    except IndexError:
+      raise ValueError('Certificate has wrong cert type')
 
-    if cert_type in (0, 1, 2, 3):
+    # Catch some invalid cert types
+    if self.type in ('RESERVED_0', 'RESERVED_1', 'RESERVED_2', 'RESERVED_3'):
       raise ValueError('Ed25519 certificate cannot have a type of %i. This is reserved to avoid conflicts with tor CERTS cells.' % cert_type)
-    elif cert_type == 4:
-      self.type = CertType.SIGNING
-    elif cert_type == 5:
-      self.type = CertType.LINK_CERT
-    elif cert_type == 6:
-      self.type = CertType.AUTH
-    elif cert_type == 7:
+    elif self.type == ('RESERVED_RSA'):
       raise ValueError('Ed25519 certificate cannot have a type of 7. This is reserved for RSA identity cross-certification.')
-    elif cert_type == 8:
-      # see rend-spec-v3.txt appendix E for these definitions
-      self.type = CertType.HS_V3_DESC_SIGNING
-    elif cert_type == 9:
-      self.type = CertType.HS_V3_INTRO_AUTH
-    elif cert_type == 0x0B:
-      self.type = CertType.HS_V3_INTRO_ENCRYPT
-    else:
-      raise ValueError('Ed25519 certificate type %i is unrecognized' % cert_type)
 
     # expiration time is in hours since epoch
     try:
@@ -333,3 +324,114 @@ class Ed25519CertificateV1(Ed25519Certificate):
       verify_key.verify(signature_bytes, descriptor_sha256_digest)
     except InvalidSignature:
       raise ValueError('Descriptor Ed25519 certificate signature invalid (Signature was forged or corrupt)')
+
+class MyED25519Certificate(object):
+  """
+  This class represents an ed25519 certificate and it's made for encoding it into a string.
+  We should merge this class with the one above.
+  """
+  def __init__(self, cert_type, expiration_date,
+               cert_key_type, certified_pub_key,
+               signing_priv_key, include_signing_key,
+               version=1):
+    """
+    :var int version
+    :var int cert_type
+    :var CertType cert_type
+    :var datetime expiration_date
+    :var int cert_key_type
+    :var ED25519PublicKey certified_pub_key
+    :var ED25519PrivateKey signing_priv_key
+    :var bool include_signing_key
+    """
+    self.version = version
+    self.cert_type = cert_type
+    self.expiration_date = expiration_date
+    self.cert_key_type = cert_key_type
+    self.certified_pub_key = certified_pub_key
+
+    self.signing_priv_key = signing_priv_key
+    self.signing_pub_key = signing_priv_key.public_key()
+
+    self.include_signing_key = include_signing_key
+    # XXX validate params
+
+  def _get_certificate_signature(self, msg_body):
+    return self.signing_priv_key.sign(msg_body)
+
+  def _get_cert_extensions_bytes(self):
+    """
+    Build the cert extensions part of the certificate
+    """
+    n_extensions = 0
+
+    # If we need to include the signing key, let's create the extension body
+    #         ExtLength [2 bytes]
+    #         ExtType   [1 byte]
+    #         ExtFlags  [1 byte]
+    #         ExtData   [ExtLength bytes]
+    if self.include_signing_key:
+      n_extensions += 1
+
+      signing_pubkey_bytes = self.signing_pub_key.public_bytes(encoding=serialization.Encoding.Raw,
+                                                               format=serialization.PublicFormat.Raw)
+
+      ext_length = len(signing_pubkey_bytes)
+      ext_type = 4
+      ext_flags = 0
+      ext_data = signing_pubkey_bytes
+
+    # Now build the actual byte representation of any extensions
+    ext_obj = bytes()
+    ext_obj += n_extensions.to_bytes(1, 'big')
+
+    if self.include_signing_key:
+      ext_obj += ext_length.to_bytes(2, 'big')
+      ext_obj += ext_type.to_bytes(1, 'big')
+      ext_obj += ext_flags.to_bytes(1, 'big')
+      ext_obj += ext_data
+
+    return ext_obj
+
+  def encode(self):
+    """Return a bytes representation of this certificate."""
+    obj = bytes()
+
+    # Encode VERSION
+    obj += self.version.to_bytes(1, 'big')
+
+    # Encode CERT_TYPE
+    try:
+      cert_type_int = CertType.index_of(self.cert_type)
+    except ValueError:
+      raise ValueError("Bad cert type %s" % self.cert_type)
+
+    obj += cert_type_int.to_bytes(1, 'big')
+
+    # Encode EXPIRATION_DATE
+    expiration_seconds_since_epoch = stem.util.datetime_to_unix(self.expiration_date)
+    expiration_hours_since_epoch = int(expiration_seconds_since_epoch) // 3600
+    obj += expiration_hours_since_epoch.to_bytes(4, 'big')
+
+    # Encode CERT_KEY_TYPE
+    obj += self.cert_key_type.to_bytes(1, 'big')
+
+    # Encode CERTIFIED_KEY
+    certified_pub_key_bytes = self.certified_pub_key.public_bytes(encoding=serialization.Encoding.Raw,
+                                                          format=serialization.PublicFormat.Raw)
+    assert(len(certified_pub_key_bytes) == 32)
+    obj += certified_pub_key_bytes
+
+    # Encode N_EXTENSIONS and EXTENSIONS
+    obj += self._get_cert_extensions_bytes()
+
+    # Do the signature on the body we have so far
+    obj += self._get_certificate_signature(obj)
+
+    return obj
+
+  def encode_for_descriptor(self):
+    cert_bytes = self.encode()
+    cert_b64 = base64.b64encode(cert_bytes)
+    cert_b64 = b'\n'.join(stem.util.str_tools._split_by_length(cert_b64, 64))
+    return b'-----BEGIN ED25519 CERT-----\n%s\n-----END ED25519 CERT-----' % cert_b64





More information about the tor-commits mailing list