Module sshkey_tools.fields

Field types for SSH Certificates

Expand source code
"""
Field types for SSH Certificates
"""
# pylint: disable=invalid-name,too-many-lines,arguments-differ
import re
from base64 import b64encode
from datetime import datetime, timedelta
from enum import Enum
from struct import pack, unpack
from typing import Tuple, Union

from cryptography.hazmat.primitives.asymmetric.utils import (
    decode_dss_signature,
    encode_dss_signature,
)

from . import exceptions as _EX
from .keys import (
    DsaPrivateKey,
    DsaPublicKey,
    EcdsaPrivateKey,
    EcdsaPublicKey,
    Ed25519PrivateKey,
    Ed25519PublicKey,
    PrivateKey,
    PublicKey,
    RsaAlgs,
    RsaPrivateKey,
    RsaPublicKey,
)
from .utils import (
    bytes_to_long,
    concat_to_string,
    ensure_bytestring,
    ensure_string,
    generate_secure_nonce,
    long_to_bytes,
    random_keyid,
    random_serial,
    str_to_time_delta,
)

NoneType = type(None)
MAX_INT32 = 2**32
MAX_INT64 = 2**64
NEWLINE = "\n"

ECDSA_CURVE_MAP = {
    "secp256r1": "nistp256",
    "secp384r1": "nistp384",
    "secp521r1": "nistp521",
}

SUBJECT_PUBKEY_MAP = {
    RsaPublicKey: "RsaPubkeyField",
    DsaPublicKey: "DsaPubkeyField",
    EcdsaPublicKey: "EcdsaPubkeyField",
    Ed25519PublicKey: "Ed25519PubkeyField",
}

CA_SIGNATURE_MAP = {
    RsaPrivateKey: "RsaSignatureField",
    DsaPrivateKey: "DsaSignatureField",
    EcdsaPrivateKey: "EcdsaSignatureField",
    Ed25519PrivateKey: "Ed25519SignatureField",
}

SIGNATURE_TYPE_MAP = {
    b"rsa": "RsaSignatureField",
    b"dss": "DsaSignatureField",
    b"ecdsa": "EcdsaSignatureField",
    b"ed25519": "Ed25519SignatureField",
}


class CERT_TYPE(Enum):
    """
    Certificate types, User certificate/Host certificate
    """

    USER = 1
    HOST = 2


class CertificateField:
    """
    The base class for certificate fields
    """

    IS_SET = None
    DEFAULT = None
    REQUIRED = False
    DATA_TYPE = NoneType

    def __init__(self, value=None):
        self.value = value
        self.exception = None
        self.IS_SET = True
        self.name = self.get_name()

    def __table__(self):
        return (str(self.name), str(self.value))

    def __str__(self):
        return f"{self.name}: {self.value}"

    def __bytes__(self) -> bytes:
        return self.encode(self.value)

    @classmethod
    def get_name(cls) -> str:
        """
        Fetch the name of the field (identifier format)

        Returns:
            str: The name/id of the field
        """
        return "_".join(re.findall("[A-Z][^A-Z]*", cls.__name__)[:-1]).lower()

    @classmethod
    def __validate_type__(cls, value, do_raise: bool = False) -> Union[bool, Exception]:
        """
        Validate the data type of the value against the class data type
        """
        if not isinstance(value, cls.DATA_TYPE):
            ex = _EX.InvalidDataException(
                f"Invalid data type for {cls.get_name()}"
                + f"(expected {cls.DATA_TYPE}, got {type(value)})"
            )

            if do_raise:
                raise ex

            return ex

        return True

    def __validate_required__(self) -> Union[bool, Exception]:
        """
        Validates if the field is set when required
        """
        if self.DEFAULT and self.value is None:
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} is a required field"
            )
        return True

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        Meant to be overridden by child classes
        """
        return True

    # pylint: disable=not-callable
    def validate(self) -> bool:
        """
        Validates all field contents and types
        """
        if isinstance(self.value, NoneType) and self.DEFAULT is not None:
            self.value = self.DEFAULT() if callable(self.DEFAULT) else self.DEFAULT

        self.exception = (
            self.__validate_type__(self.value),
            self.__validate_required__(),
            self.__validate_value__(),
        )

        return self.exception == (True, True, True)

    @staticmethod
    def decode(data: bytes) -> tuple:
        """
        Returns the decoded value of the field
        """

    @classmethod
    def encode(cls, value) -> bytes:
        """
        Returns the encoded value of the field
        """

    @classmethod
    def from_decode(cls, data: bytes) -> Tuple["CertificateField", bytes]:
        """
        Creates a field class based on encoded bytes

        Returns:
            tuple: CertificateField, remaining bytes
        """
        value, data = cls.decode(data)
        return cls(value), data

    @classmethod
    # pylint: disable=not-callable
    def factory(cls, blank: bool = False) -> "CertificateField":
        """
        Factory to create field with default value if set, otherwise empty

        Args:
            blank (bool): Return a blank class (for decoding)

        Returns:
            CertificateField: A new CertificateField subclass instance
        """
        if cls.DEFAULT is None or blank:
            return cls

        if callable(cls.DEFAULT):
            return cls(cls.DEFAULT())

        return cls(cls.DEFAULT)


class BooleanField(CertificateField):
    """
    Field representing a boolean value (True/False) or (1/0)
    """

    DATA_TYPE = (bool, int)

    @classmethod
    def encode(cls, value: Union[int, bool]) -> bytes:
        """
        Encodes a boolean value to a byte string

        Args:
            value (bool): Boolean to encode

        Returns:
            bytes: Packed byte representing the boolean
        """
        cls.__validate_type__(value, True)
        return pack("B", 1 if value else 0)

    @staticmethod
    def decode(data: bytes) -> Tuple[bool, bytes]:
        """
        Decodes a boolean from a bytestring

        Args:
            data (bytes): The byte string starting with an encoded boolean
        """
        return bool(unpack("B", data[:1])[0]), data[1:]

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        return (
            True
            if self.value in (True, False, 1, 0)
            else _EX.InvalidFieldDataException(
                f"{self.get_name()} must be a boolean (True/1 or False/0)"
            )
        )


class BytestringField(CertificateField):
    """
    Field representing a bytestring value
    """

    DATA_TYPE = (bytes, str)
    DEFAULT = b""

    @classmethod
    def encode(cls, value: bytes, encoding: str = "utf-8") -> bytes:
        """
        Encodes a string or bytestring into a packed byte string

        Args:
            value (Union[str, bytes]): The string/bytestring to encode
            encoding (str): The optional encoding, not used when passing
                            a byte value.

        Returns:
            bytes: Packed byte string containing the source data
        """
        cls.__validate_type__(value, True)
        return pack(">I", len(value)) + ensure_bytestring(value, encoding)

    @staticmethod
    def decode(data: bytes, encoding: str = None) -> Tuple[bytes, bytes]:
        """
        Unpacks the next string from a packed byte string

        Args:
            data (bytes): The packed byte string to unpack

        Returns:
            tuple(bytes, bytes):  The next block of bytes from the packed byte
                                  string and remainder of the data
        """
        length = unpack(">I", data[:4])[0] + 4

        if encoding is not None:
            return ensure_string(data[4:length], encoding), data[length:]

        return ensure_bytestring(data[4:length]), data[length:]


class StringField(BytestringField):
    """
    Field representing a string value
    """

    DATA_TYPE = (str, bytes)
    DEFAULT = ""

    @classmethod
    def encode(cls, value: str, encoding: str = "utf-8"):
        """
        Encodes a string or bytestring into a packed byte string

        Args:
            value (Union[str, bytes]): The string/bytestring to encode
            encoding (str): The encoding to user for the string

        Returns:
            bytes: Packed byte string containing the source data
        """
        return super().encode(value, encoding)

    @staticmethod
    def decode(data: bytes, encoding: str = "utf-8") -> Tuple[str, bytes]:
        """
        Unpacks the next string from a packed byte string

        Args:
            data (bytes): The packed byte string to unpack

        Returns:
            tuple(bytes, bytes):  The next block of bytes from the packed byte
                                  string and remainder of the data
        """
        return BytestringField.decode(data, encoding)


class Integer32Field(CertificateField):
    """
    Certificate field representing a 32-bit integer
    """

    DATA_TYPE = int
    DEFAULT = 0

    @classmethod
    def encode(cls, value: int) -> bytes:
        """Encodes a 32-bit integer value to a packed byte string

        Args:
            source_int (int): Integer to be packed

        Returns:
            bytes: Packed byte string containing integer
        """
        cls.__validate_type__(value, True)
        return pack(">I", value)

    @staticmethod
    def decode(data: bytes) -> Tuple[int, bytes]:
        """Decodes a 32-bit integer from a block of bytes

        Args:
            data (bytes): Block of bytes containing an integer

        Returns:
            tuple: Tuple with integer and remainder of data
        """
        return int(unpack(">I", data[:4])[0]), data[4:]

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        if self.value < MAX_INT32:
            return True

        return _EX.InvalidFieldDataException(
            f"{self.get_name()} must be a 32-bit integer"
        )


class Integer64Field(CertificateField):
    """
    Certificate field representing a 64-bit integer
    """

    DATA_TYPE = int
    DEFAULT = 0

    @classmethod
    def encode(cls, value: int) -> bytes:
        """Encodes a 64-bit integer value to a packed byte string

        Args:
            source_int (int): Integer to be packed

        Returns:
            bytes: Packed byte string containing integer
        """
        cls.__validate_type__(value, True)
        return pack(">Q", value)

    @staticmethod
    def decode(data: bytes) -> Tuple[int, bytes]:
        """Decodes a 64-bit integer from a block of bytes

        Args:
            data (bytes): Block of bytes containing an integer

        Returns:
            tuple: Tuple with integer and remainder of data
        """
        return int(unpack(">Q", data[:8])[0]), data[8:]

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        if self.value < MAX_INT64:
            return True

        return _EX.InvalidFieldDataException(
            f"{self.get_name()} must be a 64-bit integer"
        )


class DateTimeField(Integer64Field):
    """
    Certificate field representing a datetime value.
    The value is saved as a 64-bit integer (unix timestamp)
    """

    DATA_TYPE = (datetime, int, str)
    DEFAULT = datetime.now

    @classmethod
    def encode(cls, value: Union[datetime, int, str]) -> bytes:
        """Encodes a datetime object, integer or time string to a byte string
           Time strings are parsed with pytimeparse2, for example:
            32m
            2h32m
            3d2h32m
            1w3d2h32m
            1w 3d 2h 32m
            1 w 3 d 2 h 32 m
            4:13
            4:13:02
            4:13:02.266
            forever (Returns as MAX_INT64)

        Args:
            value (datetime, int, str): Datetime object

        Returns:
            bytes: Packed byte string containing datetime timestamp
        """
        cls.__validate_type__(value, True)

        if isinstance(value, str):
            if value == "forever":
                return Integer64Field.encode(MAX_INT64 - 1)

            value = int((datetime.now() + str_to_time_delta(value)).timestamp())

        if isinstance(value, datetime):
            value = int(value.timestamp())

        return Integer64Field.encode(value)

    @staticmethod
    def decode(data: bytes) -> datetime:
        """Decodes a datetime object from a block of bytes

        Args:
            data (bytes): Block of bytes containing a datetime object

        Returns:
            tuple: Tuple with datetime and remainder of data
        """
        timestamp, data = Integer64Field.decode(data)
        return datetime.fromtimestamp(timestamp), data

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        check = self.value if isinstance(self.value, int) else self.value.timestamp()

        if check < MAX_INT64:
            return True

        return _EX.InvalidFieldDataException(
            f"{self.get_name()} must be a 64-bit integer or datetime object"
        )


class MpIntegerField(BytestringField):
    """
    Certificate field representing a multiple precision integer,
    an integer too large to fit in 64 bits.
    """

    DATA_TYPE = int
    DEFAULT = 0

    @classmethod
    def encode(cls, value: int) -> bytes:
        """
        Encodes a multiprecision integer (integer larger than 64bit)
        into a packed byte string

        Args:
            value (int): Large integer

        Returns:
            bytes: Packed byte string containing integer
        """
        cls.__validate_type__(value, True)
        return BytestringField.encode(long_to_bytes(value))

    @staticmethod
    def decode(data: bytes) -> Tuple[int, bytes]:
        """Decodes a multiprecision integer (integer larger than 64bit)

        Args:
            data (bytes): Block of bytes containing a long (mp) integer

        Returns:
            tuple: Tuple with integer and remainder of data
        """
        mpint, data = BytestringField.decode(data)
        return bytes_to_long(mpint), data


class ListField(CertificateField):
    """
    Certificate field representing a list or tuple of strings
    """

    DATA_TYPE = (list, set, tuple)
    DEFAULT = []

    @classmethod
    def encode(cls, value: Union[list, tuple, set]) -> bytes:
        """Encodes a list or tuple to a byte string

        Args:
            source_list (list): list of strings
            null_separator (bool, optional): Insert blank string string between items. Default None

        Returns:
            bytes: Packed byte string containing the source data
        """
        cls.__validate_type__(value, True)

        try:
            if sum(not isinstance(item, (str, bytes)) for item in value) > 0:
                raise TypeError
        except TypeError:
            raise _EX.InvalidFieldDataException(
                "Expected list or tuple containing strings or bytes"
            ) from TypeError

        return BytestringField.encode(b"".join([StringField.encode(x) for x in value]))

    @staticmethod
    def decode(data: bytes) -> Tuple[list, bytes]:
        """Decodes a list of strings from a block of bytes

        Args:
            data (bytes): The block of bytes containing a list of strings
        Returns:
            tuple: _description_
        """
        list_bytes, data = BytestringField.decode(data)

        decoded = []
        while len(list_bytes) > 0:
            elem, list_bytes = StringField.decode(list_bytes)
            decoded.append(elem)

        return ensure_string(decoded), data

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        if hasattr(self.value, "__iter__") and not all(
            (isinstance(val, (str, bytes)) for val in self.value)
        ):
            return _EX.InvalidFieldDataException(
                "Expected list or tuple containing strings or bytes"
            )
        return True


class KeyValueField(CertificateField):
    """
    Certificate field representing a list or integer in python,
    separated in byte-form by null-bytes.
    """

    DATA_TYPE = (list, tuple, set, dict)
    DEFAULT = {}

    @classmethod
    def encode(cls, value: Union[list, tuple, dict, set]) -> bytes:
        """
        Encodes a dict, set, list or tuple into a key-value byte string.
        If a set, list or tuple is provided, the items are considered keys
        and added with empty values.

        Args:
            source_list (dict, set, list, tuple): list of strings

        Returns:
            bytes: Packed byte string containing the source data
        """
        cls.__validate_type__(value, True)

        if not isinstance(value, dict):
            value = {item: "" for item in value}

        list_data = b""

        for key, item in value.items():
            list_data += StringField.encode(key)

            item = (
                StringField.encode("")
                if item in ["", b""]
                else ListField.encode(
                    [item] if isinstance(item, (str, bytes)) else item
                )
            )

            list_data += item

        return BytestringField.encode(list_data)

    @staticmethod
    def decode(data: bytes) -> Tuple[dict, bytes]:
        """Decodes a list of strings from a block of bytes

        Args:
            data (bytes): The block of bytes containing a list of strings
        Returns:
            tuple: _description_
        """
        list_bytes, data = BytestringField.decode(data)

        decoded = {}
        while len(list_bytes) > 0:
            key, list_bytes = StringField.decode(list_bytes)
            value, list_bytes = BytestringField.decode(list_bytes)

            if value != b"":
                value = StringField.decode(value)[0]

            decoded[key] = value

        decoded = ensure_string(decoded)

        if "".join(decoded.values()) == "":
            return list(decoded.keys()), data

        return decoded, data

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        testvals = (
            self.value
            if not isinstance(self.value, dict)
            else list(self.value.keys()) + list(self.value.values())
        )

        if hasattr(self.value, "__iter__") and not all(
            (isinstance(val, (str, bytes)) for val in testvals)
        ):
            return _EX.InvalidFieldDataException(
                "Expected dict, list, tuple, set with string or byte keys and values"
            )

        return True


class PubkeyTypeField(StringField):
    """
    Contains the certificate type, which is based on the
    public key type the certificate is created for, e.g.
    'ssh-ed25519-cert-v01@openssh.com' for an ED25519 key
    """

    DEFAULT = None
    DATA_TYPE = (str, bytes)
    ALLOWED_VALUES = (
        "ssh-rsa-cert-v01@openssh.com",
        "rsa-sha2-256-cert-v01@openssh.com",
        "rsa-sha2-512-cert-v01@openssh.com",
        "ssh-dss-cert-v01@openssh.com",
        "ecdsa-sha2-nistp256-cert-v01@openssh.com",
        "ecdsa-sha2-nistp384-cert-v01@openssh.com",
        "ecdsa-sha2-nistp521-cert-v01@openssh.com",
        "ssh-ed25519-cert-v01@openssh.com",
    )

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        if ensure_string(self.value) not in self.ALLOWED_VALUES:
            return _EX.InvalidFieldDataException(
                "Expected one of the following values: "
                + NEWLINE.join(self.ALLOWED_VALUES)
            )

        return True


class NonceField(BytestringField):
    """
    Contains the nonce for the certificate, randomly generated
    this protects the integrity of the private key, especially
    for ecdsa.
    """

    DEFAULT = generate_secure_nonce
    DATA_TYPE = (str, bytes)

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        if hasattr(self.value, "__count__") and len(self.value) < 32:
            return _EX.InvalidFieldDataException(
                "Expected a nonce of at least 32 bytes"
            )

        return True


class PublicKeyField(CertificateField):
    """
    Contains the subject (User or Host) public key for whom/which
    the certificate is created.
    """

    DEFAULT = None
    DATA_TYPE = PublicKey

    def __table__(self) -> tuple:
        return [str(self.name), str(self.value.get_fingerprint())]

    def __str__(self) -> str:
        return " ".join(
            [
                self.__class__.__name__.replace("PubkeyField", ""),
                self.value.get_fingerprint(),
            ]
        )

    @classmethod
    def encode(cls, value: PublicKey) -> bytes:
        """
        Encode the certificate field to a byte string

        Args:
            value (RsaPublicKey): The public key to encode

        Returns:
            bytes: A byte string with the encoded public key
        """
        cls.__validate_type__(value, True)
        return BytestringField.decode(value.raw_bytes())[1]

    @staticmethod
    def from_object(public_key: PublicKey):
        """
        Loads the public key from a sshkey_tools.keys.PublicKey
        class or childclass

        Args:
            public_key (PublicKey): The public key for which to
                                    create the certificate

        Raises:
            _EX.InvalidKeyException: Invalid public key

        Returns:
            PublicKeyField: A child class of PublicKeyField specific
                            to the chosen public key
        """
        try:
            return globals()[SUBJECT_PUBKEY_MAP[public_key.__class__]](value=public_key)
        except KeyError:
            raise _EX.InvalidKeyException("The public key is invalid") from KeyError


class RsaPubkeyField(PublicKeyField):
    """
    Holds the RSA Public Key for RSA Certificates
    """

    DEFAULT = None
    DATA_TYPE = RsaPublicKey

    @staticmethod
    def decode(data: bytes) -> Tuple[RsaPublicKey, bytes]:
        """
        Decode the certificate field from a byte string
        starting with the encoded public key

        Args:
            data (bytes): The byte string starting with the encoded key

        Returns:
            Tuple[RsaPublicKey, bytes]: The PublicKey field and remainder of the data
        """
        e, data = MpIntegerField.decode(data)
        n, data = MpIntegerField.decode(data)

        return RsaPublicKey.from_numbers(e=e, n=n), data


class DsaPubkeyField(PublicKeyField):
    """
    Holds the DSA Public Key for DSA Certificates
    """

    DEFAULT = None
    DATA_TYPE = DsaPublicKey

    @staticmethod
    def decode(data: bytes) -> Tuple[DsaPublicKey, bytes]:
        """
        Decode the certificate field from a byte string
        starting with the encoded public key

        Args:
            data (bytes): The byte string starting with the encoded key

        Returns:
            Tuple[RsaPublicKey, bytes]: The PublicKey field and remainder of the data
        """
        p, data = MpIntegerField.decode(data)
        q, data = MpIntegerField.decode(data)
        g, data = MpIntegerField.decode(data)
        y, data = MpIntegerField.decode(data)

        return DsaPublicKey.from_numbers(p=p, q=q, g=g, y=y), data


class EcdsaPubkeyField(PublicKeyField):
    """
    Holds the ECDSA Public Key for ECDSA Certificates
    """

    DEFAULT = None
    DATA_TYPE = EcdsaPublicKey

    @staticmethod
    def decode(data: bytes) -> Tuple[EcdsaPublicKey, bytes]:
        """
        Decode the certificate field from a byte string
        starting with the encoded public key

        Args:
            data (bytes): The byte string starting with the encoded key

        Returns:
            Tuple[ECPublicKey, bytes]: The PublicKey field and remainder of the data
        """
        curve, data = StringField.decode(data)
        key, data = BytestringField.decode(data)

        key_type = "ecdsa-sha2-" + curve

        return (
            EcdsaPublicKey.from_string(
                key_type
                + " "
                + b64encode(
                    StringField.encode(key_type)
                    + StringField.encode(curve)
                    + BytestringField.encode(key)
                ).decode("utf-8")
            ),
            data,
        )


class Ed25519PubkeyField(PublicKeyField):
    """
    Holds the ED25519 Public Key for ED25519 Certificates
    """

    DEFAULT = None
    DATA_TYPE = Ed25519PublicKey

    @staticmethod
    def decode(data: bytes) -> Tuple[Ed25519PublicKey, bytes]:
        """
        Decode the certificate field from a byte string
        starting with the encoded public key

        Args:
            data (bytes): The byte string starting with the encoded key

        Returns:
            Tuple[Ed25519PublicKey, bytes]: The PublicKey field and remainder of the data
        """
        pubkey, data = BytestringField.decode(data)

        return Ed25519PublicKey.from_raw_bytes(pubkey), data


class SerialField(Integer64Field):
    """
    Contains the numeric serial number of the certificate,
    maximum is (2**64)-1
    """

    DEFAULT = random_serial
    DATA_TYPE = int


class CertificateTypeField(Integer32Field):
    """
    Contains the certificate type
    User certificate: CERT_TYPE.USER/1
    Host certificate: CERT_TYPE.HOST/2
    """

    DEFAULT = CERT_TYPE.USER
    DATA_TYPE = (CERT_TYPE, int)
    ALLOWED_VALUES = (CERT_TYPE.USER, CERT_TYPE.HOST, 1, 2)

    @classmethod
    def encode(cls, value: Union[CERT_TYPE, int]) -> bytes:
        """
        Encode the certificate type field to a byte string

        Args:
            value (Union[CERT_TYPE, int]): The type of the certificate

        Returns:
            bytes: A byte string with the encoded public key
        """
        cls.__validate_type__(value, True)

        if isinstance(value, CERT_TYPE):
            value = value.value

        return Integer32Field.encode(value)

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        if self.value not in self.ALLOWED_VALUES:
            return _EX.InvalidCertificateFieldException(
                "The certificate type is invalid (expected int(1,2) or CERT_TYPE.X)"
            )

        return True


class KeyIdField(StringField):
    """
    Contains the key identifier (subject) of the certificate,
    alphanumeric string
    """

    DEFAULT = random_keyid
    DATA_TYPE = (str, bytes)


class PrincipalsField(ListField):
    """
    Contains a list of principals for the certificate,
    e.g. SERVERHOSTNAME01 or all-web-servers.
    If no principals are added, the certificate is valid
    only for servers that have no allowed principals specified
    """

    DEFAFULT = []
    DATA_TYPE = (list, set, tuple)


class ValidAfterField(DateTimeField):
    """
    Contains the start of the validity period for the certificate,
    represented by a datetime object
    """

    DEFAULT = datetime.now()
    DATA_TYPE = (datetime, int)


class ValidBeforeField(DateTimeField):
    """
    Contains the end of the validity period for the certificate,
    represented by a datetime object
    """

    DEFAULT = datetime.now() + timedelta(minutes=10)
    DATA_TYPE = (datetime, int)

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        Additional checks over standard datetime field are
        done to ensure no already expired certificates are
        created
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        super().__validate_value__()
        check = (
            self.value
            if isinstance(self.value, datetime)
            else datetime.fromtimestamp(self.value)
        )

        if check < datetime.now():
            return _EX.InvalidCertificateFieldException(
                "The certificate validity period is invalid"
                + " (expected a future datetime object or timestamp)"
            )

        return True


class CriticalOptionsField(KeyValueField):
    """
    Contains the critical options part of the certificate (optional).
    This should be a list of strings with one of the following

    options:
        force-command=<command>
            Limits the connecting user to a specific command,
            e.g. sftp-internal
        source-address=<ip_address>
            Limits the user to connect only from a certain
            ip, subnet or host
        verify-required=<true|false>
            If set to true, the user must verify their identity
            if using a hardware token
    """

    DEFAULT = []
    DATA_TYPE = (list, set, tuple, dict)
    ALLOWED_VALUES = ("force-command", "source-address", "verify-required")

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        for elem in (
            self.value if not isinstance(self.value, dict) else list(self.value.keys())
        ):
            if elem not in self.ALLOWED_VALUES:
                return _EX.InvalidCertificateFieldException(
                    f"Critical option not recognized ({elem}){NEWLINE}"
                    + f"Valid options are {', '.join(self.ALLOWED_VALUES)}"
                )

        return True


class ExtensionsField(KeyValueField):
    """
    Contains a list of extensions for the certificate,
    set to give the user limitations and/or additional
    privileges on the host.

    flags:
        no-touch-required
            The user doesn't need to touch the
            physical key to authenticate.

        permit-X11-forwarding
            Permits the user to use X11 Forwarding

        permit-agent-forwarding
            Permits the user to use agent forwarding

        permit-port-forwarding
            Permits the user to forward ports

        permit-pty
            Permits the user to use a pseudo-terminal

        permit-user-rc
            Permits the user to use the user rc file

    """

    DEFAULT = []
    DATA_TYPE = (list, set, tuple, dict)
    ALLOWED_VALUES = (
        "no-touch-required",
        "permit-X11-forwarding",
        "permit-agent-forwarding",
        "permit-port-forwarding",
        "permit-pty",
        "permit-user-rc",
    )

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        for item in self.value:
            if item not in self.ALLOWED_VALUES:
                return _EX.InvalidDataException(
                    f"Invalid extension '{item}'{NEWLINE}"
                    + f"Allowed values are: {NEWLINE.join(self.ALLOWED_VALUES)}"
                )

        return True


class ReservedField(StringField):
    """
    This field is reserved for future use, and
    doesn't contain any actual data, just an empty string.
    """

    DEFAULT = ""
    DATA_TYPE = str

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        return (
            True
            if self.value == ""
            else _EX.InvalidDataException("The reserved field is not empty")
        )


class CAPublicKeyField(BytestringField):
    """
    Contains the public key of the certificate authority
    that is used to sign the certificate.
    """

    DEFAULT = None
    DATA_TYPE = (str, bytes)

    def __str__(self) -> str:
        return " ".join(
            [
                (
                    self.value.__class__.__name__.replace("PublicKey", "").replace(
                        "EllipticCurve", "ECDSA"
                    )
                ),
                self.value.get_fingerprint(),
            ]
        )

    def __bytes__(self) -> bytes:
        return self.encode(self.value.raw_bytes())

    def __table__(self) -> tuple:
        return ("CA Public Key", self.value.get_fingerprint())

    def validate(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if self.value in [None, False, "", " "]:
            return _EX.InvalidFieldDataException("You need to provide a CA public key")

        if not isinstance(self.value, PublicKey):
            return _EX.InvalidFieldDataException(
                "The CA public key needs to be a sshkey_tools.keys.PublicKey object"
            )

        return True

    @staticmethod
    def decode(data: bytes) -> Tuple[PublicKey, bytes]:
        """
        Decode the certificate field from a byte string
        starting with the encoded public key

        Args:
            data (bytes): The byte string starting with the encoded key

        Returns:
            Tuple[PublicKey, bytes]: The PublicKey field and remainder of the data
        """
        pubkey, data = BytestringField.decode(data)
        pubkey_type = StringField.decode(pubkey)[0]

        return (
            PublicKey.from_string(
                concat_to_string(pubkey_type, " ", b64encode(pubkey))
            ),
            data,
        )

    @classmethod
    def from_object(cls, public_key: PublicKey) -> "CAPublicKeyField":
        """
        Creates a new CAPublicKeyField from a PublicKey object
        """
        return cls(value=public_key)


class SignatureField(CertificateField):
    """
    Creates and contains the signature of the certificate
    """

    DEFAULT = None
    DATA_TYPE = bytes

    # pylint: disable=super-init-not-called
    def __init__(self, private_key: PrivateKey = None, signature: bytes = None):
        self.private_key = private_key
        self.is_signed = False
        self.value = signature

        if signature is not None and ensure_bytestring(signature) not in ("", " "):
            self.is_signed = True

    def __table__(self) -> tuple:
        msg = "No signature"
        if self.is_signed and self.private_key is not None:
            msg = f"Signed with private key {self.private_key.get_fingerprint()}"
        
        if self.is_signed and self.private_key is None:
            msg = "Signed with: See pubkey fingerprint above"

        return ("Signature", msg)

    @staticmethod
    def from_object(private_key: PrivateKey):
        """
        Load a private key from a PrivateKey object

        Args:
            private_key (PrivateKey): Private key to use for signing

        Raises:
            _EX.InvalidKeyException: Invalid private key

        Returns:
            SignatureField: SignatureField child class
        """
        try:
            return globals()[CA_SIGNATURE_MAP[private_key.__class__]](
                private_key=private_key
            )
        except KeyError:
            raise _EX.InvalidKeyException(
                "The private key provided is invalid or not supported"
            ) from KeyError

    @staticmethod
    def from_decode(data: bytes) -> Tuple["SignatureField", bytes]:
        """
        Generates a SignatureField child class from the encoded signature

        Args:
            data (bytes): The bytestring containing the encoded signature

        Raises:
            _EX.InvalidDataException: Invalid data

        Returns:
            SignatureField: child of SignatureField
        """
        signature, _ = BytestringField.decode(data)
        signature_type = BytestringField.decode(signature)[0]

        for key, value in SIGNATURE_TYPE_MAP.items():
            if key in signature_type:
                return globals()[value].from_decode(data)

        raise _EX.InvalidDataException("No matching signature type found")

    def can_sign(self):
        """
        Determines if a signature can be generated from
        this private key
        """
        return self.private_key is not None

    def sign(self, data: bytes, **kwargs) -> None:
        """
        Placeholder signing function
        """
        raise _EX.InvalidClassCallException("The base class has no sign function")

    def __bytes__(self) -> None:
        return self.encode(self.value)


class RsaSignatureField(SignatureField):
    """
    Creates and contains the RSA signature from an RSA Private Key
    """

    DEFAULT = None
    DATA_TYPE = bytes

    def __init__(
        self,
        private_key: RsaPrivateKey = None,
        hash_alg: RsaAlgs = RsaAlgs.SHA512,
        signature: bytes = None,
    ):
        super().__init__(private_key, signature)
        self.hash_alg = hash_alg

    @classmethod
    # pylint: disable=arguments-renamed
    def encode(cls, value: bytes, hash_alg: RsaAlgs = RsaAlgs.SHA512) -> bytes:
        """
        Encodes the value to a byte string

        Args:
            signature (bytes): The signature bytes to encode
            hash_alg (RsaAlgs, optional):  The hash algorithm used for the signature.
                                            Defaults to RsaAlgs.SHA256.

        Returns:
            bytes: The encoded byte string
        """
        cls.__validate_type__(value, True)

        return BytestringField.encode(
            StringField.encode(hash_alg.value[0]) + BytestringField.encode(value)
        )

    @staticmethod
    def decode(data: bytes) -> Tuple[Tuple[bytes, bytes], bytes]:
        """
        Decodes a bytestring containing a signature

        Args:
            data (bytes): The bytestring starting with the RSA Signature

        Returns:
            Tuple[ Tuple[ bytes, bytes ], bytes ]: (signature_type, signature), remainder of data
        """
        signature, data = BytestringField.decode(data)

        sig_type, signature = StringField.decode(signature)
        signature, _ = BytestringField.decode(signature)

        return (sig_type, signature), data

    @classmethod
    def from_decode(cls, data: bytes) -> Tuple["RsaSignatureField", bytes]:
        """
        Generates an RsaSignatureField class from the encoded signature

        Args:
            data (bytes): The bytestring containing the encoded signature

        Raises:
            _EX.InvalidDataException: Invalid data

        Returns:
            Tuple[RsaSignatureField, bytes]: RSA Signature field and remainder of data
        """
        signature, data = cls.decode(data)

        return (
            cls(
                private_key=None,
                hash_alg=[alg for alg in RsaAlgs if alg.value[0] == signature[0]][0],
                signature=signature[1],
            ),
            data,
        )

    # pylint: disable=unused-argument
    def sign(self, data: bytes, hash_alg: RsaAlgs = RsaAlgs.SHA512, **kwargs) -> None:
        """
        Signs the provided data with the provided private key

        Args:
            data (bytes): The data to be signed
            hash_alg (RsaAlgs, optional): The RSA algorithm to use for hashing.
                                           Defaults to RsaAlgs.SHA256.
        """
        self.value = self.private_key.sign(data, hash_alg)

        self.hash_alg = hash_alg
        self.is_signed = True

    def __bytes__(self):
        return self.encode(self.value, self.hash_alg)


class DsaSignatureField(SignatureField):
    """
    Creates and contains the DSA signature from an DSA Private Key
    """

    DEFAULT = None
    DATA_TYPE = bytes

    def __init__(
        self, private_key: DsaPrivateKey = None, signature: bytes = None
    ) -> None:
        super().__init__(private_key, signature)

    @classmethod
    def encode(cls, value: bytes):
        """
        Encodes the signature to a byte string

        Args:
            signature (bytes): The signature bytes to encode

        Returns:
            bytes: The encoded byte string
        """
        cls.__validate_type__(value, True)

        r, s = decode_dss_signature(value)

        return BytestringField.encode(
            StringField.encode("ssh-dss")
            + BytestringField.encode(long_to_bytes(r, 20) + long_to_bytes(s, 20))
        )

    @staticmethod
    def decode(data: bytes) -> Tuple[bytes, bytes]:
        """
        Decodes a bytestring containing a signature

        Args:
            data (bytes): The bytestring starting with the Signature

        Returns:
            Tuple[ bytes, bytes ]: signature, remainder of the data
        """
        signature, data = BytestringField.decode(data)

        signature = BytestringField.decode(BytestringField.decode(signature)[1])[0]
        r = bytes_to_long(signature[:20])
        s = bytes_to_long(signature[20:])

        signature = encode_dss_signature(r, s)

        return signature, data

    @classmethod
    def from_decode(cls, data: bytes) -> Tuple["DsaSignatureField", bytes]:
        """
        Creates a signature field class from the encoded signature

        Args:
            data (bytes): The bytestring starting with the Signature

        Returns:
            Tuple[ DsaSignatureField, bytes ]: signature, remainder of the data
        """
        signature, data = cls.decode(data)

        return cls(private_key=None, signature=signature), data

    # pylint: disable=unused-argument
    def sign(self, data: bytes, **kwargs) -> None:
        """
        Signs the provided data with the provided private key

        Args:
            data (bytes): The data to be signed
        """
        self.value = self.private_key.sign(data)
        self.is_signed = True


class EcdsaSignatureField(SignatureField):
    """
    Creates and contains the ECDSA signature from an ECDSA Private Key
    """

    DEFAULT = None
    DATA_TYPE = bytes

    def __init__(
        self,
        private_key: EcdsaPrivateKey = None,
        signature: bytes = None,
        curve_name: str = None,
    ) -> None:
        super().__init__(private_key, signature)

        if curve_name is None:
            curve_size = self.private_key.public_key.key.curve.key_size
            curve_name = f"ecdsa-sha2-nistp{curve_size}"

        self.curve = curve_name

    @classmethod
    def encode(cls, value: bytes, curve_name: str = None) -> bytes:
        """
        Encodes the signature to a byte string

        Args:
            signature (bytes): The signature bytes to encode
            curve_name (str): The name of the curve used for the signature
                              private key

        Returns:
            bytes: The encoded byte string
        """
        cls.__validate_type__(value, True)

        r, s = decode_dss_signature(value)

        return BytestringField.encode(
            StringField.encode(curve_name)
            + BytestringField.encode(
                MpIntegerField.encode(r) + MpIntegerField.encode(s)
            )
        )

    @staticmethod
    def decode(data: bytes) -> Tuple[Tuple[bytes, bytes], bytes]:
        """
        Decodes a bytestring containing a signature

        Args:
            data (bytes): The bytestring starting with the Signature

        Returns:
            Tuple[ Tuple[ bytes, bytes ], bytes]: (curve, signature), remainder of the data
        """
        signature, data = BytestringField.decode(data)

        curve, signature = StringField.decode(signature)
        signature, _ = BytestringField.decode(signature)

        r, signature = MpIntegerField.decode(signature)
        s, _ = MpIntegerField.decode(signature)

        signature = encode_dss_signature(r, s)

        return (curve, signature), data

    @classmethod
    def from_decode(cls, data: bytes) -> Tuple["EcdsaSignatureField", bytes]:
        """
        Creates a signature field class from the encoded signature

        Args:
            data (bytes): The bytestring starting with the Signature

        Returns:
            Tuple[ EcdsaSignatureField , bytes ]: signature, remainder of the data
        """
        signature, data = cls.decode(data)

        return (
            cls(private_key=None, signature=signature[1], curve_name=signature[0]),
            data,
        )

    # pylint: disable=unused-argument
    def sign(self, data: bytes, **kwargs) -> None:
        """
        Signs the provided data with the provided private key

        Args:
            data (bytes): The data to be signed
        """
        self.value = self.private_key.sign(data)
        self.is_signed = True

    def __bytes__(self):
        return self.encode(self.value, self.curve)


class Ed25519SignatureField(SignatureField):
    """
    Creates and contains the ED25519 signature from an ED25519 Private Key
    """

    DEFAULT = None
    DATA_TYPE = bytes

    def __init__(
        self,
        # trunk-ignore(gitleaks/generic-api-key)
        private_key: Ed25519PrivateKey = None,
        signature: bytes = None,
    ) -> None:
        super().__init__(private_key, signature)

    @classmethod
    def encode(cls, value: bytes) -> None:
        """
        Encodes the signature to a byte string

        Args:
            signature (bytes): The signature bytes to encode

        Returns:
            bytes: The encoded byte string
        """
        cls.__validate_type__(value, True)

        return BytestringField.encode(
            StringField.encode("ssh-ed25519") + BytestringField.encode(value)
        )

    @staticmethod
    def decode(data: bytes) -> Tuple[bytes, bytes]:
        """
        Decodes a bytestring containing a signature

        Args:
            data (bytes): The bytestring starting with the Signature

        Returns:
            Tuple[ bytes, bytes ]: signature, remainder of the data
        """
        signature, data = BytestringField.decode(data)

        signature = BytestringField.decode(BytestringField.decode(signature)[1])[0]

        return signature, data

    @classmethod
    def from_decode(cls, data: bytes) -> Tuple["Ed25519SignatureField", bytes]:
        """
        Creates a signature field class from the encoded signature

        Args:
            data (bytes): The bytestring starting with the Signature

        Returns:
            Tuple[ Ed25519SignatureField , bytes ]: signature, remainder of the data
        """
        signature, data = cls.decode(data)

        return cls(private_key=None, signature=signature), data

    # pylint: disable=unused-argument
    def sign(self, data: bytes, **kwargs) -> None:
        """
        Signs the provided data with the provided private key

        Args:
            data (bytes): The data to be signed
            hash_alg (RsaAlgs, optional): The RSA algorithm to use for hashing.
                                           Defaults to RsaAlgs.SHA256.
        """
        self.value = self.private_key.sign(data)
        self.is_signed = True

Functions

def decode_dss_signature(...)
def encode_dss_signature(...)

Classes

class BooleanField (value=None)

Field representing a boolean value (True/False) or (1/0)

Expand source code
class BooleanField(CertificateField):
    """
    Field representing a boolean value (True/False) or (1/0)
    """

    DATA_TYPE = (bool, int)

    @classmethod
    def encode(cls, value: Union[int, bool]) -> bytes:
        """
        Encodes a boolean value to a byte string

        Args:
            value (bool): Boolean to encode

        Returns:
            bytes: Packed byte representing the boolean
        """
        cls.__validate_type__(value, True)
        return pack("B", 1 if value else 0)

    @staticmethod
    def decode(data: bytes) -> Tuple[bool, bytes]:
        """
        Decodes a boolean from a bytestring

        Args:
            data (bytes): The byte string starting with an encoded boolean
        """
        return bool(unpack("B", data[:1])[0]), data[1:]

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        return (
            True
            if self.value in (True, False, 1, 0)
            else _EX.InvalidFieldDataException(
                f"{self.get_name()} must be a boolean (True/1 or False/0)"
            )
        )

Ancestors

Class variables

var DATA_TYPE

Static methods

def decode(data: bytes) ‑> Tuple[bool, bytes]

Decodes a boolean from a bytestring

Args

data : bytes
The byte string starting with an encoded boolean
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[bool, bytes]:
    """
    Decodes a boolean from a bytestring

    Args:
        data (bytes): The byte string starting with an encoded boolean
    """
    return bool(unpack("B", data[:1])[0]), data[1:]
def encode(value: Union[int, bool]) ‑> bytes

Encodes a boolean value to a byte string

Args

value : bool
Boolean to encode

Returns

bytes
Packed byte representing the boolean
Expand source code
@classmethod
def encode(cls, value: Union[int, bool]) -> bytes:
    """
    Encodes a boolean value to a byte string

    Args:
        value (bool): Boolean to encode

    Returns:
        bytes: Packed byte representing the boolean
    """
    cls.__validate_type__(value, True)
    return pack("B", 1 if value else 0)

Inherited members

class BytestringField (value=None)

Field representing a bytestring value

Expand source code
class BytestringField(CertificateField):
    """
    Field representing a bytestring value
    """

    DATA_TYPE = (bytes, str)
    DEFAULT = b""

    @classmethod
    def encode(cls, value: bytes, encoding: str = "utf-8") -> bytes:
        """
        Encodes a string or bytestring into a packed byte string

        Args:
            value (Union[str, bytes]): The string/bytestring to encode
            encoding (str): The optional encoding, not used when passing
                            a byte value.

        Returns:
            bytes: Packed byte string containing the source data
        """
        cls.__validate_type__(value, True)
        return pack(">I", len(value)) + ensure_bytestring(value, encoding)

    @staticmethod
    def decode(data: bytes, encoding: str = None) -> Tuple[bytes, bytes]:
        """
        Unpacks the next string from a packed byte string

        Args:
            data (bytes): The packed byte string to unpack

        Returns:
            tuple(bytes, bytes):  The next block of bytes from the packed byte
                                  string and remainder of the data
        """
        length = unpack(">I", data[:4])[0] + 4

        if encoding is not None:
            return ensure_string(data[4:length], encoding), data[length:]

        return ensure_bytestring(data[4:length]), data[length:]

Ancestors

Subclasses

Class variables

var DATA_TYPE
var DEFAULT

Static methods

def decode(data: bytes, encoding: str = None) ‑> Tuple[bytes, bytes]

Unpacks the next string from a packed byte string

Args

data : bytes
The packed byte string to unpack

Returns

tuple(bytes, bytes): The next block of bytes from the packed byte string and remainder of the data

Expand source code
@staticmethod
def decode(data: bytes, encoding: str = None) -> Tuple[bytes, bytes]:
    """
    Unpacks the next string from a packed byte string

    Args:
        data (bytes): The packed byte string to unpack

    Returns:
        tuple(bytes, bytes):  The next block of bytes from the packed byte
                              string and remainder of the data
    """
    length = unpack(">I", data[:4])[0] + 4

    if encoding is not None:
        return ensure_string(data[4:length], encoding), data[length:]

    return ensure_bytestring(data[4:length]), data[length:]
def encode(value: bytes, encoding: str = 'utf-8') ‑> bytes

Encodes a string or bytestring into a packed byte string

Args

value : Union[str, bytes]
The string/bytestring to encode
encoding : str
The optional encoding, not used when passing a byte value.

Returns

bytes
Packed byte string containing the source data
Expand source code
@classmethod
def encode(cls, value: bytes, encoding: str = "utf-8") -> bytes:
    """
    Encodes a string or bytestring into a packed byte string

    Args:
        value (Union[str, bytes]): The string/bytestring to encode
        encoding (str): The optional encoding, not used when passing
                        a byte value.

    Returns:
        bytes: Packed byte string containing the source data
    """
    cls.__validate_type__(value, True)
    return pack(">I", len(value)) + ensure_bytestring(value, encoding)

Inherited members

class CAPublicKeyField (value=None)

Contains the public key of the certificate authority that is used to sign the certificate.

Expand source code
class CAPublicKeyField(BytestringField):
    """
    Contains the public key of the certificate authority
    that is used to sign the certificate.
    """

    DEFAULT = None
    DATA_TYPE = (str, bytes)

    def __str__(self) -> str:
        return " ".join(
            [
                (
                    self.value.__class__.__name__.replace("PublicKey", "").replace(
                        "EllipticCurve", "ECDSA"
                    )
                ),
                self.value.get_fingerprint(),
            ]
        )

    def __bytes__(self) -> bytes:
        return self.encode(self.value.raw_bytes())

    def __table__(self) -> tuple:
        return ("CA Public Key", self.value.get_fingerprint())

    def validate(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if self.value in [None, False, "", " "]:
            return _EX.InvalidFieldDataException("You need to provide a CA public key")

        if not isinstance(self.value, PublicKey):
            return _EX.InvalidFieldDataException(
                "The CA public key needs to be a sshkey_tools.keys.PublicKey object"
            )

        return True

    @staticmethod
    def decode(data: bytes) -> Tuple[PublicKey, bytes]:
        """
        Decode the certificate field from a byte string
        starting with the encoded public key

        Args:
            data (bytes): The byte string starting with the encoded key

        Returns:
            Tuple[PublicKey, bytes]: The PublicKey field and remainder of the data
        """
        pubkey, data = BytestringField.decode(data)
        pubkey_type = StringField.decode(pubkey)[0]

        return (
            PublicKey.from_string(
                concat_to_string(pubkey_type, " ", b64encode(pubkey))
            ),
            data,
        )

    @classmethod
    def from_object(cls, public_key: PublicKey) -> "CAPublicKeyField":
        """
        Creates a new CAPublicKeyField from a PublicKey object
        """
        return cls(value=public_key)

Ancestors

Class variables

var DATA_TYPE
var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[PublicKey, bytes]

Decode the certificate field from a byte string starting with the encoded public key

Args

data : bytes
The byte string starting with the encoded key

Returns

Tuple[PublicKey, bytes]
The PublicKey field and remainder of the data
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[PublicKey, bytes]:
    """
    Decode the certificate field from a byte string
    starting with the encoded public key

    Args:
        data (bytes): The byte string starting with the encoded key

    Returns:
        Tuple[PublicKey, bytes]: The PublicKey field and remainder of the data
    """
    pubkey, data = BytestringField.decode(data)
    pubkey_type = StringField.decode(pubkey)[0]

    return (
        PublicKey.from_string(
            concat_to_string(pubkey_type, " ", b64encode(pubkey))
        ),
        data,
    )
def from_object(public_key: PublicKey) ‑> CAPublicKeyField

Creates a new CAPublicKeyField from a PublicKey object

Expand source code
@classmethod
def from_object(cls, public_key: PublicKey) -> "CAPublicKeyField":
    """
    Creates a new CAPublicKeyField from a PublicKey object
    """
    return cls(value=public_key)

Methods

def validate(self) ‑> Union[bool, Exception]

Validates the contents of the field

Expand source code
def validate(self) -> Union[bool, Exception]:
    """
    Validates the contents of the field
    """
    if self.value in [None, False, "", " "]:
        return _EX.InvalidFieldDataException("You need to provide a CA public key")

    if not isinstance(self.value, PublicKey):
        return _EX.InvalidFieldDataException(
            "The CA public key needs to be a sshkey_tools.keys.PublicKey object"
        )

    return True

Inherited members

class CERT_TYPE (value, names=None, *, module=None, qualname=None, type=None, start=1)

Certificate types, User certificate/Host certificate

Expand source code
class CERT_TYPE(Enum):
    """
    Certificate types, User certificate/Host certificate
    """

    USER = 1
    HOST = 2

Ancestors

  • enum.Enum

Class variables

var HOST
var USER
class CertificateField (value=None)

The base class for certificate fields

Expand source code
class CertificateField:
    """
    The base class for certificate fields
    """

    IS_SET = None
    DEFAULT = None
    REQUIRED = False
    DATA_TYPE = NoneType

    def __init__(self, value=None):
        self.value = value
        self.exception = None
        self.IS_SET = True
        self.name = self.get_name()

    def __table__(self):
        return (str(self.name), str(self.value))

    def __str__(self):
        return f"{self.name}: {self.value}"

    def __bytes__(self) -> bytes:
        return self.encode(self.value)

    @classmethod
    def get_name(cls) -> str:
        """
        Fetch the name of the field (identifier format)

        Returns:
            str: The name/id of the field
        """
        return "_".join(re.findall("[A-Z][^A-Z]*", cls.__name__)[:-1]).lower()

    @classmethod
    def __validate_type__(cls, value, do_raise: bool = False) -> Union[bool, Exception]:
        """
        Validate the data type of the value against the class data type
        """
        if not isinstance(value, cls.DATA_TYPE):
            ex = _EX.InvalidDataException(
                f"Invalid data type for {cls.get_name()}"
                + f"(expected {cls.DATA_TYPE}, got {type(value)})"
            )

            if do_raise:
                raise ex

            return ex

        return True

    def __validate_required__(self) -> Union[bool, Exception]:
        """
        Validates if the field is set when required
        """
        if self.DEFAULT and self.value is None:
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} is a required field"
            )
        return True

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        Meant to be overridden by child classes
        """
        return True

    # pylint: disable=not-callable
    def validate(self) -> bool:
        """
        Validates all field contents and types
        """
        if isinstance(self.value, NoneType) and self.DEFAULT is not None:
            self.value = self.DEFAULT() if callable(self.DEFAULT) else self.DEFAULT

        self.exception = (
            self.__validate_type__(self.value),
            self.__validate_required__(),
            self.__validate_value__(),
        )

        return self.exception == (True, True, True)

    @staticmethod
    def decode(data: bytes) -> tuple:
        """
        Returns the decoded value of the field
        """

    @classmethod
    def encode(cls, value) -> bytes:
        """
        Returns the encoded value of the field
        """

    @classmethod
    def from_decode(cls, data: bytes) -> Tuple["CertificateField", bytes]:
        """
        Creates a field class based on encoded bytes

        Returns:
            tuple: CertificateField, remaining bytes
        """
        value, data = cls.decode(data)
        return cls(value), data

    @classmethod
    # pylint: disable=not-callable
    def factory(cls, blank: bool = False) -> "CertificateField":
        """
        Factory to create field with default value if set, otherwise empty

        Args:
            blank (bool): Return a blank class (for decoding)

        Returns:
            CertificateField: A new CertificateField subclass instance
        """
        if cls.DEFAULT is None or blank:
            return cls

        if callable(cls.DEFAULT):
            return cls(cls.DEFAULT())

        return cls(cls.DEFAULT)

Subclasses

Class variables

var DATA_TYPE
var DEFAULT
var IS_SET
var REQUIRED

Static methods

def decode(data: bytes) ‑> tuple

Returns the decoded value of the field

Expand source code
@staticmethod
def decode(data: bytes) -> tuple:
    """
    Returns the decoded value of the field
    """
def encode(value) ‑> bytes

Returns the encoded value of the field

Expand source code
@classmethod
def encode(cls, value) -> bytes:
    """
    Returns the encoded value of the field
    """
def factory(blank: bool = False) ‑> CertificateField

Factory to create field with default value if set, otherwise empty

Args

blank : bool
Return a blank class (for decoding)

Returns

CertificateField
A new CertificateField subclass instance
Expand source code
@classmethod
# pylint: disable=not-callable
def factory(cls, blank: bool = False) -> "CertificateField":
    """
    Factory to create field with default value if set, otherwise empty

    Args:
        blank (bool): Return a blank class (for decoding)

    Returns:
        CertificateField: A new CertificateField subclass instance
    """
    if cls.DEFAULT is None or blank:
        return cls

    if callable(cls.DEFAULT):
        return cls(cls.DEFAULT())

    return cls(cls.DEFAULT)
def from_decode(data: bytes) ‑> Tuple[CertificateField, bytes]

Creates a field class based on encoded bytes

Returns

tuple
CertificateField, remaining bytes
Expand source code
@classmethod
def from_decode(cls, data: bytes) -> Tuple["CertificateField", bytes]:
    """
    Creates a field class based on encoded bytes

    Returns:
        tuple: CertificateField, remaining bytes
    """
    value, data = cls.decode(data)
    return cls(value), data
def get_name() ‑> str

Fetch the name of the field (identifier format)

Returns

str
The name/id of the field
Expand source code
@classmethod
def get_name(cls) -> str:
    """
    Fetch the name of the field (identifier format)

    Returns:
        str: The name/id of the field
    """
    return "_".join(re.findall("[A-Z][^A-Z]*", cls.__name__)[:-1]).lower()

Methods

def validate(self) ‑> bool

Validates all field contents and types

Expand source code
def validate(self) -> bool:
    """
    Validates all field contents and types
    """
    if isinstance(self.value, NoneType) and self.DEFAULT is not None:
        self.value = self.DEFAULT() if callable(self.DEFAULT) else self.DEFAULT

    self.exception = (
        self.__validate_type__(self.value),
        self.__validate_required__(),
        self.__validate_value__(),
    )

    return self.exception == (True, True, True)
class CertificateTypeField (value=None)

Contains the certificate type User certificate: CERT_TYPE.USER/1 Host certificate: CERT_TYPE.HOST/2

Expand source code
class CertificateTypeField(Integer32Field):
    """
    Contains the certificate type
    User certificate: CERT_TYPE.USER/1
    Host certificate: CERT_TYPE.HOST/2
    """

    DEFAULT = CERT_TYPE.USER
    DATA_TYPE = (CERT_TYPE, int)
    ALLOWED_VALUES = (CERT_TYPE.USER, CERT_TYPE.HOST, 1, 2)

    @classmethod
    def encode(cls, value: Union[CERT_TYPE, int]) -> bytes:
        """
        Encode the certificate type field to a byte string

        Args:
            value (Union[CERT_TYPE, int]): The type of the certificate

        Returns:
            bytes: A byte string with the encoded public key
        """
        cls.__validate_type__(value, True)

        if isinstance(value, CERT_TYPE):
            value = value.value

        return Integer32Field.encode(value)

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        if self.value not in self.ALLOWED_VALUES:
            return _EX.InvalidCertificateFieldException(
                "The certificate type is invalid (expected int(1,2) or CERT_TYPE.X)"
            )

        return True

Ancestors

Class variables

var ALLOWED_VALUES
var DEFAULT

Static methods

def encode(value: Union[CERT_TYPE, int]) ‑> bytes

Encode the certificate type field to a byte string

Args

value : Union[CERT_TYPE, int]
The type of the certificate

Returns

bytes
A byte string with the encoded public key
Expand source code
@classmethod
def encode(cls, value: Union[CERT_TYPE, int]) -> bytes:
    """
    Encode the certificate type field to a byte string

    Args:
        value (Union[CERT_TYPE, int]): The type of the certificate

    Returns:
        bytes: A byte string with the encoded public key
    """
    cls.__validate_type__(value, True)

    if isinstance(value, CERT_TYPE):
        value = value.value

    return Integer32Field.encode(value)

Inherited members

class CriticalOptionsField (value=None)

Contains the critical options part of the certificate (optional). This should be a list of strings with one of the following

options: force-command= Limits the connecting user to a specific command, e.g. sftp-internal source-address= Limits the user to connect only from a certain ip, subnet or host verify-required= If set to true, the user must verify their identity if using a hardware token

Expand source code
class CriticalOptionsField(KeyValueField):
    """
    Contains the critical options part of the certificate (optional).
    This should be a list of strings with one of the following

    options:
        force-command=<command>
            Limits the connecting user to a specific command,
            e.g. sftp-internal
        source-address=<ip_address>
            Limits the user to connect only from a certain
            ip, subnet or host
        verify-required=<true|false>
            If set to true, the user must verify their identity
            if using a hardware token
    """

    DEFAULT = []
    DATA_TYPE = (list, set, tuple, dict)
    ALLOWED_VALUES = ("force-command", "source-address", "verify-required")

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        for elem in (
            self.value if not isinstance(self.value, dict) else list(self.value.keys())
        ):
            if elem not in self.ALLOWED_VALUES:
                return _EX.InvalidCertificateFieldException(
                    f"Critical option not recognized ({elem}){NEWLINE}"
                    + f"Valid options are {', '.join(self.ALLOWED_VALUES)}"
                )

        return True

Ancestors

Class variables

var ALLOWED_VALUES
var DATA_TYPE
var DEFAULT

Inherited members

class DateTimeField (value=None)

Certificate field representing a datetime value. The value is saved as a 64-bit integer (unix timestamp)

Expand source code
class DateTimeField(Integer64Field):
    """
    Certificate field representing a datetime value.
    The value is saved as a 64-bit integer (unix timestamp)
    """

    DATA_TYPE = (datetime, int, str)
    DEFAULT = datetime.now

    @classmethod
    def encode(cls, value: Union[datetime, int, str]) -> bytes:
        """Encodes a datetime object, integer or time string to a byte string
           Time strings are parsed with pytimeparse2, for example:
            32m
            2h32m
            3d2h32m
            1w3d2h32m
            1w 3d 2h 32m
            1 w 3 d 2 h 32 m
            4:13
            4:13:02
            4:13:02.266
            forever (Returns as MAX_INT64)

        Args:
            value (datetime, int, str): Datetime object

        Returns:
            bytes: Packed byte string containing datetime timestamp
        """
        cls.__validate_type__(value, True)

        if isinstance(value, str):
            if value == "forever":
                return Integer64Field.encode(MAX_INT64 - 1)

            value = int((datetime.now() + str_to_time_delta(value)).timestamp())

        if isinstance(value, datetime):
            value = int(value.timestamp())

        return Integer64Field.encode(value)

    @staticmethod
    def decode(data: bytes) -> datetime:
        """Decodes a datetime object from a block of bytes

        Args:
            data (bytes): Block of bytes containing a datetime object

        Returns:
            tuple: Tuple with datetime and remainder of data
        """
        timestamp, data = Integer64Field.decode(data)
        return datetime.fromtimestamp(timestamp), data

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        check = self.value if isinstance(self.value, int) else self.value.timestamp()

        if check < MAX_INT64:
            return True

        return _EX.InvalidFieldDataException(
            f"{self.get_name()} must be a 64-bit integer or datetime object"
        )

Ancestors

Subclasses

Static methods

def decode(data: bytes) ‑> datetime.datetime

Decodes a datetime object from a block of bytes

Args

data : bytes
Block of bytes containing a datetime object

Returns

tuple
Tuple with datetime and remainder of data
Expand source code
@staticmethod
def decode(data: bytes) -> datetime:
    """Decodes a datetime object from a block of bytes

    Args:
        data (bytes): Block of bytes containing a datetime object

    Returns:
        tuple: Tuple with datetime and remainder of data
    """
    timestamp, data = Integer64Field.decode(data)
    return datetime.fromtimestamp(timestamp), data
def encode(value: Union[datetime.datetime, int, str]) ‑> bytes

Encodes a datetime object, integer or time string to a byte string Time strings are parsed with pytimeparse2, for example: 32m 2h32m 3d2h32m 1w3d2h32m 1w 3d 2h 32m 1 w 3 d 2 h 32 m 4:13 4:13:02 4:13:02.266 forever (Returns as MAX_INT64)

Args

value : datetime, int, str
Datetime object

Returns

bytes
Packed byte string containing datetime timestamp
Expand source code
@classmethod
def encode(cls, value: Union[datetime, int, str]) -> bytes:
    """Encodes a datetime object, integer or time string to a byte string
       Time strings are parsed with pytimeparse2, for example:
        32m
        2h32m
        3d2h32m
        1w3d2h32m
        1w 3d 2h 32m
        1 w 3 d 2 h 32 m
        4:13
        4:13:02
        4:13:02.266
        forever (Returns as MAX_INT64)

    Args:
        value (datetime, int, str): Datetime object

    Returns:
        bytes: Packed byte string containing datetime timestamp
    """
    cls.__validate_type__(value, True)

    if isinstance(value, str):
        if value == "forever":
            return Integer64Field.encode(MAX_INT64 - 1)

        value = int((datetime.now() + str_to_time_delta(value)).timestamp())

    if isinstance(value, datetime):
        value = int(value.timestamp())

    return Integer64Field.encode(value)

Methods

def DEFAULT(tz=None)

Returns new datetime object representing current time local to tz.

tz Timezone object.

If no tz is specified, uses local timezone.

Inherited members

class DsaPubkeyField (value=None)

Holds the DSA Public Key for DSA Certificates

Expand source code
class DsaPubkeyField(PublicKeyField):
    """
    Holds the DSA Public Key for DSA Certificates
    """

    DEFAULT = None
    DATA_TYPE = DsaPublicKey

    @staticmethod
    def decode(data: bytes) -> Tuple[DsaPublicKey, bytes]:
        """
        Decode the certificate field from a byte string
        starting with the encoded public key

        Args:
            data (bytes): The byte string starting with the encoded key

        Returns:
            Tuple[RsaPublicKey, bytes]: The PublicKey field and remainder of the data
        """
        p, data = MpIntegerField.decode(data)
        q, data = MpIntegerField.decode(data)
        g, data = MpIntegerField.decode(data)
        y, data = MpIntegerField.decode(data)

        return DsaPublicKey.from_numbers(p=p, q=q, g=g, y=y), data

Ancestors

Class variables

var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[DsaPublicKey, bytes]

Decode the certificate field from a byte string starting with the encoded public key

Args

data : bytes
The byte string starting with the encoded key

Returns

Tuple[RsaPublicKey, bytes]
The PublicKey field and remainder of the data
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[DsaPublicKey, bytes]:
    """
    Decode the certificate field from a byte string
    starting with the encoded public key

    Args:
        data (bytes): The byte string starting with the encoded key

    Returns:
        Tuple[RsaPublicKey, bytes]: The PublicKey field and remainder of the data
    """
    p, data = MpIntegerField.decode(data)
    q, data = MpIntegerField.decode(data)
    g, data = MpIntegerField.decode(data)
    y, data = MpIntegerField.decode(data)

    return DsaPublicKey.from_numbers(p=p, q=q, g=g, y=y), data

Inherited members

class DsaSignatureField (private_key: DsaPrivateKey = None, signature: bytes = None)

Creates and contains the DSA signature from an DSA Private Key

Expand source code
class DsaSignatureField(SignatureField):
    """
    Creates and contains the DSA signature from an DSA Private Key
    """

    DEFAULT = None
    DATA_TYPE = bytes

    def __init__(
        self, private_key: DsaPrivateKey = None, signature: bytes = None
    ) -> None:
        super().__init__(private_key, signature)

    @classmethod
    def encode(cls, value: bytes):
        """
        Encodes the signature to a byte string

        Args:
            signature (bytes): The signature bytes to encode

        Returns:
            bytes: The encoded byte string
        """
        cls.__validate_type__(value, True)

        r, s = decode_dss_signature(value)

        return BytestringField.encode(
            StringField.encode("ssh-dss")
            + BytestringField.encode(long_to_bytes(r, 20) + long_to_bytes(s, 20))
        )

    @staticmethod
    def decode(data: bytes) -> Tuple[bytes, bytes]:
        """
        Decodes a bytestring containing a signature

        Args:
            data (bytes): The bytestring starting with the Signature

        Returns:
            Tuple[ bytes, bytes ]: signature, remainder of the data
        """
        signature, data = BytestringField.decode(data)

        signature = BytestringField.decode(BytestringField.decode(signature)[1])[0]
        r = bytes_to_long(signature[:20])
        s = bytes_to_long(signature[20:])

        signature = encode_dss_signature(r, s)

        return signature, data

    @classmethod
    def from_decode(cls, data: bytes) -> Tuple["DsaSignatureField", bytes]:
        """
        Creates a signature field class from the encoded signature

        Args:
            data (bytes): The bytestring starting with the Signature

        Returns:
            Tuple[ DsaSignatureField, bytes ]: signature, remainder of the data
        """
        signature, data = cls.decode(data)

        return cls(private_key=None, signature=signature), data

    # pylint: disable=unused-argument
    def sign(self, data: bytes, **kwargs) -> None:
        """
        Signs the provided data with the provided private key

        Args:
            data (bytes): The data to be signed
        """
        self.value = self.private_key.sign(data)
        self.is_signed = True

Ancestors

Class variables

var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[bytes, bytes]

Decodes a bytestring containing a signature

Args

data : bytes
The bytestring starting with the Signature

Returns

Tuple[ bytes, bytes ]
signature, remainder of the data
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[bytes, bytes]:
    """
    Decodes a bytestring containing a signature

    Args:
        data (bytes): The bytestring starting with the Signature

    Returns:
        Tuple[ bytes, bytes ]: signature, remainder of the data
    """
    signature, data = BytestringField.decode(data)

    signature = BytestringField.decode(BytestringField.decode(signature)[1])[0]
    r = bytes_to_long(signature[:20])
    s = bytes_to_long(signature[20:])

    signature = encode_dss_signature(r, s)

    return signature, data
def encode(value: bytes)

Encodes the signature to a byte string

Args

signature : bytes
The signature bytes to encode

Returns

bytes
The encoded byte string
Expand source code
@classmethod
def encode(cls, value: bytes):
    """
    Encodes the signature to a byte string

    Args:
        signature (bytes): The signature bytes to encode

    Returns:
        bytes: The encoded byte string
    """
    cls.__validate_type__(value, True)

    r, s = decode_dss_signature(value)

    return BytestringField.encode(
        StringField.encode("ssh-dss")
        + BytestringField.encode(long_to_bytes(r, 20) + long_to_bytes(s, 20))
    )
def from_decode(data: bytes) ‑> Tuple[DsaSignatureField, bytes]

Creates a signature field class from the encoded signature

Args

data : bytes
The bytestring starting with the Signature

Returns

Tuple[ DsaSignatureField, bytes ]
signature, remainder of the data
Expand source code
@classmethod
def from_decode(cls, data: bytes) -> Tuple["DsaSignatureField", bytes]:
    """
    Creates a signature field class from the encoded signature

    Args:
        data (bytes): The bytestring starting with the Signature

    Returns:
        Tuple[ DsaSignatureField, bytes ]: signature, remainder of the data
    """
    signature, data = cls.decode(data)

    return cls(private_key=None, signature=signature), data

Methods

def sign(self, data: bytes, **kwargs) ‑> None

Signs the provided data with the provided private key

Args

data : bytes
The data to be signed
Expand source code
def sign(self, data: bytes, **kwargs) -> None:
    """
    Signs the provided data with the provided private key

    Args:
        data (bytes): The data to be signed
    """
    self.value = self.private_key.sign(data)
    self.is_signed = True

Inherited members

class EcdsaPubkeyField (value=None)

Holds the ECDSA Public Key for ECDSA Certificates

Expand source code
class EcdsaPubkeyField(PublicKeyField):
    """
    Holds the ECDSA Public Key for ECDSA Certificates
    """

    DEFAULT = None
    DATA_TYPE = EcdsaPublicKey

    @staticmethod
    def decode(data: bytes) -> Tuple[EcdsaPublicKey, bytes]:
        """
        Decode the certificate field from a byte string
        starting with the encoded public key

        Args:
            data (bytes): The byte string starting with the encoded key

        Returns:
            Tuple[ECPublicKey, bytes]: The PublicKey field and remainder of the data
        """
        curve, data = StringField.decode(data)
        key, data = BytestringField.decode(data)

        key_type = "ecdsa-sha2-" + curve

        return (
            EcdsaPublicKey.from_string(
                key_type
                + " "
                + b64encode(
                    StringField.encode(key_type)
                    + StringField.encode(curve)
                    + BytestringField.encode(key)
                ).decode("utf-8")
            ),
            data,
        )

Ancestors

Class variables

var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[EcdsaPublicKey, bytes]

Decode the certificate field from a byte string starting with the encoded public key

Args

data : bytes
The byte string starting with the encoded key

Returns

Tuple[ECPublicKey, bytes]
The PublicKey field and remainder of the data
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[EcdsaPublicKey, bytes]:
    """
    Decode the certificate field from a byte string
    starting with the encoded public key

    Args:
        data (bytes): The byte string starting with the encoded key

    Returns:
        Tuple[ECPublicKey, bytes]: The PublicKey field and remainder of the data
    """
    curve, data = StringField.decode(data)
    key, data = BytestringField.decode(data)

    key_type = "ecdsa-sha2-" + curve

    return (
        EcdsaPublicKey.from_string(
            key_type
            + " "
            + b64encode(
                StringField.encode(key_type)
                + StringField.encode(curve)
                + BytestringField.encode(key)
            ).decode("utf-8")
        ),
        data,
    )

Inherited members

class EcdsaSignatureField (private_key: EcdsaPrivateKey = None, signature: bytes = None, curve_name: str = None)

Creates and contains the ECDSA signature from an ECDSA Private Key

Expand source code
class EcdsaSignatureField(SignatureField):
    """
    Creates and contains the ECDSA signature from an ECDSA Private Key
    """

    DEFAULT = None
    DATA_TYPE = bytes

    def __init__(
        self,
        private_key: EcdsaPrivateKey = None,
        signature: bytes = None,
        curve_name: str = None,
    ) -> None:
        super().__init__(private_key, signature)

        if curve_name is None:
            curve_size = self.private_key.public_key.key.curve.key_size
            curve_name = f"ecdsa-sha2-nistp{curve_size}"

        self.curve = curve_name

    @classmethod
    def encode(cls, value: bytes, curve_name: str = None) -> bytes:
        """
        Encodes the signature to a byte string

        Args:
            signature (bytes): The signature bytes to encode
            curve_name (str): The name of the curve used for the signature
                              private key

        Returns:
            bytes: The encoded byte string
        """
        cls.__validate_type__(value, True)

        r, s = decode_dss_signature(value)

        return BytestringField.encode(
            StringField.encode(curve_name)
            + BytestringField.encode(
                MpIntegerField.encode(r) + MpIntegerField.encode(s)
            )
        )

    @staticmethod
    def decode(data: bytes) -> Tuple[Tuple[bytes, bytes], bytes]:
        """
        Decodes a bytestring containing a signature

        Args:
            data (bytes): The bytestring starting with the Signature

        Returns:
            Tuple[ Tuple[ bytes, bytes ], bytes]: (curve, signature), remainder of the data
        """
        signature, data = BytestringField.decode(data)

        curve, signature = StringField.decode(signature)
        signature, _ = BytestringField.decode(signature)

        r, signature = MpIntegerField.decode(signature)
        s, _ = MpIntegerField.decode(signature)

        signature = encode_dss_signature(r, s)

        return (curve, signature), data

    @classmethod
    def from_decode(cls, data: bytes) -> Tuple["EcdsaSignatureField", bytes]:
        """
        Creates a signature field class from the encoded signature

        Args:
            data (bytes): The bytestring starting with the Signature

        Returns:
            Tuple[ EcdsaSignatureField , bytes ]: signature, remainder of the data
        """
        signature, data = cls.decode(data)

        return (
            cls(private_key=None, signature=signature[1], curve_name=signature[0]),
            data,
        )

    # pylint: disable=unused-argument
    def sign(self, data: bytes, **kwargs) -> None:
        """
        Signs the provided data with the provided private key

        Args:
            data (bytes): The data to be signed
        """
        self.value = self.private_key.sign(data)
        self.is_signed = True

    def __bytes__(self):
        return self.encode(self.value, self.curve)

Ancestors

Class variables

var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[Tuple[bytes, bytes], bytes]

Decodes a bytestring containing a signature

Args

data : bytes
The bytestring starting with the Signature

Returns

Tuple[ Tuple[ bytes, bytes ], bytes]
(curve, signature), remainder of the data
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[Tuple[bytes, bytes], bytes]:
    """
    Decodes a bytestring containing a signature

    Args:
        data (bytes): The bytestring starting with the Signature

    Returns:
        Tuple[ Tuple[ bytes, bytes ], bytes]: (curve, signature), remainder of the data
    """
    signature, data = BytestringField.decode(data)

    curve, signature = StringField.decode(signature)
    signature, _ = BytestringField.decode(signature)

    r, signature = MpIntegerField.decode(signature)
    s, _ = MpIntegerField.decode(signature)

    signature = encode_dss_signature(r, s)

    return (curve, signature), data
def encode(value: bytes, curve_name: str = None) ‑> bytes

Encodes the signature to a byte string

Args

signature : bytes
The signature bytes to encode
curve_name : str
The name of the curve used for the signature private key

Returns

bytes
The encoded byte string
Expand source code
@classmethod
def encode(cls, value: bytes, curve_name: str = None) -> bytes:
    """
    Encodes the signature to a byte string

    Args:
        signature (bytes): The signature bytes to encode
        curve_name (str): The name of the curve used for the signature
                          private key

    Returns:
        bytes: The encoded byte string
    """
    cls.__validate_type__(value, True)

    r, s = decode_dss_signature(value)

    return BytestringField.encode(
        StringField.encode(curve_name)
        + BytestringField.encode(
            MpIntegerField.encode(r) + MpIntegerField.encode(s)
        )
    )
def from_decode(data: bytes) ‑> Tuple[EcdsaSignatureField, bytes]

Creates a signature field class from the encoded signature

Args

data : bytes
The bytestring starting with the Signature

Returns

Tuple[ EcdsaSignatureField , bytes ]
signature, remainder of the data
Expand source code
@classmethod
def from_decode(cls, data: bytes) -> Tuple["EcdsaSignatureField", bytes]:
    """
    Creates a signature field class from the encoded signature

    Args:
        data (bytes): The bytestring starting with the Signature

    Returns:
        Tuple[ EcdsaSignatureField , bytes ]: signature, remainder of the data
    """
    signature, data = cls.decode(data)

    return (
        cls(private_key=None, signature=signature[1], curve_name=signature[0]),
        data,
    )

Methods

def sign(self, data: bytes, **kwargs) ‑> None

Signs the provided data with the provided private key

Args

data : bytes
The data to be signed
Expand source code
def sign(self, data: bytes, **kwargs) -> None:
    """
    Signs the provided data with the provided private key

    Args:
        data (bytes): The data to be signed
    """
    self.value = self.private_key.sign(data)
    self.is_signed = True

Inherited members

class Ed25519PubkeyField (value=None)

Holds the ED25519 Public Key for ED25519 Certificates

Expand source code
class Ed25519PubkeyField(PublicKeyField):
    """
    Holds the ED25519 Public Key for ED25519 Certificates
    """

    DEFAULT = None
    DATA_TYPE = Ed25519PublicKey

    @staticmethod
    def decode(data: bytes) -> Tuple[Ed25519PublicKey, bytes]:
        """
        Decode the certificate field from a byte string
        starting with the encoded public key

        Args:
            data (bytes): The byte string starting with the encoded key

        Returns:
            Tuple[Ed25519PublicKey, bytes]: The PublicKey field and remainder of the data
        """
        pubkey, data = BytestringField.decode(data)

        return Ed25519PublicKey.from_raw_bytes(pubkey), data

Ancestors

Class variables

var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[Ed25519PublicKey, bytes]

Decode the certificate field from a byte string starting with the encoded public key

Args

data : bytes
The byte string starting with the encoded key

Returns

Tuple[Ed25519PublicKey, bytes]
The PublicKey field and remainder of the data
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[Ed25519PublicKey, bytes]:
    """
    Decode the certificate field from a byte string
    starting with the encoded public key

    Args:
        data (bytes): The byte string starting with the encoded key

    Returns:
        Tuple[Ed25519PublicKey, bytes]: The PublicKey field and remainder of the data
    """
    pubkey, data = BytestringField.decode(data)

    return Ed25519PublicKey.from_raw_bytes(pubkey), data

Inherited members

class Ed25519SignatureField (private_key: Ed25519PrivateKey = None, signature: bytes = None)

Creates and contains the ED25519 signature from an ED25519 Private Key

Expand source code
class Ed25519SignatureField(SignatureField):
    """
    Creates and contains the ED25519 signature from an ED25519 Private Key
    """

    DEFAULT = None
    DATA_TYPE = bytes

    def __init__(
        self,
        # trunk-ignore(gitleaks/generic-api-key)
        private_key: Ed25519PrivateKey = None,
        signature: bytes = None,
    ) -> None:
        super().__init__(private_key, signature)

    @classmethod
    def encode(cls, value: bytes) -> None:
        """
        Encodes the signature to a byte string

        Args:
            signature (bytes): The signature bytes to encode

        Returns:
            bytes: The encoded byte string
        """
        cls.__validate_type__(value, True)

        return BytestringField.encode(
            StringField.encode("ssh-ed25519") + BytestringField.encode(value)
        )

    @staticmethod
    def decode(data: bytes) -> Tuple[bytes, bytes]:
        """
        Decodes a bytestring containing a signature

        Args:
            data (bytes): The bytestring starting with the Signature

        Returns:
            Tuple[ bytes, bytes ]: signature, remainder of the data
        """
        signature, data = BytestringField.decode(data)

        signature = BytestringField.decode(BytestringField.decode(signature)[1])[0]

        return signature, data

    @classmethod
    def from_decode(cls, data: bytes) -> Tuple["Ed25519SignatureField", bytes]:
        """
        Creates a signature field class from the encoded signature

        Args:
            data (bytes): The bytestring starting with the Signature

        Returns:
            Tuple[ Ed25519SignatureField , bytes ]: signature, remainder of the data
        """
        signature, data = cls.decode(data)

        return cls(private_key=None, signature=signature), data

    # pylint: disable=unused-argument
    def sign(self, data: bytes, **kwargs) -> None:
        """
        Signs the provided data with the provided private key

        Args:
            data (bytes): The data to be signed
            hash_alg (RsaAlgs, optional): The RSA algorithm to use for hashing.
                                           Defaults to RsaAlgs.SHA256.
        """
        self.value = self.private_key.sign(data)
        self.is_signed = True

Ancestors

Class variables

var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[bytes, bytes]

Decodes a bytestring containing a signature

Args

data : bytes
The bytestring starting with the Signature

Returns

Tuple[ bytes, bytes ]
signature, remainder of the data
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[bytes, bytes]:
    """
    Decodes a bytestring containing a signature

    Args:
        data (bytes): The bytestring starting with the Signature

    Returns:
        Tuple[ bytes, bytes ]: signature, remainder of the data
    """
    signature, data = BytestringField.decode(data)

    signature = BytestringField.decode(BytestringField.decode(signature)[1])[0]

    return signature, data
def encode(value: bytes) ‑> None

Encodes the signature to a byte string

Args

signature : bytes
The signature bytes to encode

Returns

bytes
The encoded byte string
Expand source code
@classmethod
def encode(cls, value: bytes) -> None:
    """
    Encodes the signature to a byte string

    Args:
        signature (bytes): The signature bytes to encode

    Returns:
        bytes: The encoded byte string
    """
    cls.__validate_type__(value, True)

    return BytestringField.encode(
        StringField.encode("ssh-ed25519") + BytestringField.encode(value)
    )
def from_decode(data: bytes) ‑> Tuple[Ed25519SignatureField, bytes]

Creates a signature field class from the encoded signature

Args

data : bytes
The bytestring starting with the Signature

Returns

Tuple[ Ed25519SignatureField , bytes ]
signature, remainder of the data
Expand source code
@classmethod
def from_decode(cls, data: bytes) -> Tuple["Ed25519SignatureField", bytes]:
    """
    Creates a signature field class from the encoded signature

    Args:
        data (bytes): The bytestring starting with the Signature

    Returns:
        Tuple[ Ed25519SignatureField , bytes ]: signature, remainder of the data
    """
    signature, data = cls.decode(data)

    return cls(private_key=None, signature=signature), data

Methods

def sign(self, data: bytes, **kwargs) ‑> None

Signs the provided data with the provided private key

Args

data : bytes
The data to be signed
hash_alg : RsaAlgs, optional
The RSA algorithm to use for hashing. Defaults to RsaAlgs.SHA256.
Expand source code
def sign(self, data: bytes, **kwargs) -> None:
    """
    Signs the provided data with the provided private key

    Args:
        data (bytes): The data to be signed
        hash_alg (RsaAlgs, optional): The RSA algorithm to use for hashing.
                                       Defaults to RsaAlgs.SHA256.
    """
    self.value = self.private_key.sign(data)
    self.is_signed = True

Inherited members

class ExtensionsField (value=None)

Contains a list of extensions for the certificate, set to give the user limitations and/or additional privileges on the host.

flags: no-touch-required The user doesn't need to touch the physical key to authenticate.

permit-X11-forwarding
    Permits the user to use X11 Forwarding

permit-agent-forwarding
    Permits the user to use agent forwarding

permit-port-forwarding
    Permits the user to forward ports

permit-pty
    Permits the user to use a pseudo-terminal

permit-user-rc
    Permits the user to use the user rc file
Expand source code
class ExtensionsField(KeyValueField):
    """
    Contains a list of extensions for the certificate,
    set to give the user limitations and/or additional
    privileges on the host.

    flags:
        no-touch-required
            The user doesn't need to touch the
            physical key to authenticate.

        permit-X11-forwarding
            Permits the user to use X11 Forwarding

        permit-agent-forwarding
            Permits the user to use agent forwarding

        permit-port-forwarding
            Permits the user to forward ports

        permit-pty
            Permits the user to use a pseudo-terminal

        permit-user-rc
            Permits the user to use the user rc file

    """

    DEFAULT = []
    DATA_TYPE = (list, set, tuple, dict)
    ALLOWED_VALUES = (
        "no-touch-required",
        "permit-X11-forwarding",
        "permit-agent-forwarding",
        "permit-port-forwarding",
        "permit-pty",
        "permit-user-rc",
    )

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        for item in self.value:
            if item not in self.ALLOWED_VALUES:
                return _EX.InvalidDataException(
                    f"Invalid extension '{item}'{NEWLINE}"
                    + f"Allowed values are: {NEWLINE.join(self.ALLOWED_VALUES)}"
                )

        return True

Ancestors

Class variables

var ALLOWED_VALUES
var DATA_TYPE
var DEFAULT

Inherited members

class Integer32Field (value=None)

Certificate field representing a 32-bit integer

Expand source code
class Integer32Field(CertificateField):
    """
    Certificate field representing a 32-bit integer
    """

    DATA_TYPE = int
    DEFAULT = 0

    @classmethod
    def encode(cls, value: int) -> bytes:
        """Encodes a 32-bit integer value to a packed byte string

        Args:
            source_int (int): Integer to be packed

        Returns:
            bytes: Packed byte string containing integer
        """
        cls.__validate_type__(value, True)
        return pack(">I", value)

    @staticmethod
    def decode(data: bytes) -> Tuple[int, bytes]:
        """Decodes a 32-bit integer from a block of bytes

        Args:
            data (bytes): Block of bytes containing an integer

        Returns:
            tuple: Tuple with integer and remainder of data
        """
        return int(unpack(">I", data[:4])[0]), data[4:]

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        if self.value < MAX_INT32:
            return True

        return _EX.InvalidFieldDataException(
            f"{self.get_name()} must be a 32-bit integer"
        )

Ancestors

Subclasses

Class variables

var DATA_TYPE

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.

>>> int('0b100', base=0)
4
var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[int, bytes]

Decodes a 32-bit integer from a block of bytes

Args

data : bytes
Block of bytes containing an integer

Returns

tuple
Tuple with integer and remainder of data
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[int, bytes]:
    """Decodes a 32-bit integer from a block of bytes

    Args:
        data (bytes): Block of bytes containing an integer

    Returns:
        tuple: Tuple with integer and remainder of data
    """
    return int(unpack(">I", data[:4])[0]), data[4:]
def encode(value: int) ‑> bytes

Encodes a 32-bit integer value to a packed byte string

Args

source_int : int
Integer to be packed

Returns

bytes
Packed byte string containing integer
Expand source code
@classmethod
def encode(cls, value: int) -> bytes:
    """Encodes a 32-bit integer value to a packed byte string

    Args:
        source_int (int): Integer to be packed

    Returns:
        bytes: Packed byte string containing integer
    """
    cls.__validate_type__(value, True)
    return pack(">I", value)

Inherited members

class Integer64Field (value=None)

Certificate field representing a 64-bit integer

Expand source code
class Integer64Field(CertificateField):
    """
    Certificate field representing a 64-bit integer
    """

    DATA_TYPE = int
    DEFAULT = 0

    @classmethod
    def encode(cls, value: int) -> bytes:
        """Encodes a 64-bit integer value to a packed byte string

        Args:
            source_int (int): Integer to be packed

        Returns:
            bytes: Packed byte string containing integer
        """
        cls.__validate_type__(value, True)
        return pack(">Q", value)

    @staticmethod
    def decode(data: bytes) -> Tuple[int, bytes]:
        """Decodes a 64-bit integer from a block of bytes

        Args:
            data (bytes): Block of bytes containing an integer

        Returns:
            tuple: Tuple with integer and remainder of data
        """
        return int(unpack(">Q", data[:8])[0]), data[8:]

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        if self.value < MAX_INT64:
            return True

        return _EX.InvalidFieldDataException(
            f"{self.get_name()} must be a 64-bit integer"
        )

Ancestors

Subclasses

Class variables

var DATA_TYPE

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.

>>> int('0b100', base=0)
4
var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[int, bytes]

Decodes a 64-bit integer from a block of bytes

Args

data : bytes
Block of bytes containing an integer

Returns

tuple
Tuple with integer and remainder of data
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[int, bytes]:
    """Decodes a 64-bit integer from a block of bytes

    Args:
        data (bytes): Block of bytes containing an integer

    Returns:
        tuple: Tuple with integer and remainder of data
    """
    return int(unpack(">Q", data[:8])[0]), data[8:]
def encode(value: int) ‑> bytes

Encodes a 64-bit integer value to a packed byte string

Args

source_int : int
Integer to be packed

Returns

bytes
Packed byte string containing integer
Expand source code
@classmethod
def encode(cls, value: int) -> bytes:
    """Encodes a 64-bit integer value to a packed byte string

    Args:
        source_int (int): Integer to be packed

    Returns:
        bytes: Packed byte string containing integer
    """
    cls.__validate_type__(value, True)
    return pack(">Q", value)

Inherited members

class KeyIdField (value=None)

Contains the key identifier (subject) of the certificate, alphanumeric string

Expand source code
class KeyIdField(StringField):
    """
    Contains the key identifier (subject) of the certificate,
    alphanumeric string
    """

    DEFAULT = random_keyid
    DATA_TYPE = (str, bytes)

Ancestors

Class variables

var DATA_TYPE

Methods

def DEFAULT() ‑> str

Generates a random Key ID

Returns

str
Random keyid
Expand source code
def random_keyid() -> str:
    """Generates a random Key ID

    Returns:
        str: Random keyid
    """
    return str(uuid4())

Inherited members

class KeyValueField (value=None)

Certificate field representing a list or integer in python, separated in byte-form by null-bytes.

Expand source code
class KeyValueField(CertificateField):
    """
    Certificate field representing a list or integer in python,
    separated in byte-form by null-bytes.
    """

    DATA_TYPE = (list, tuple, set, dict)
    DEFAULT = {}

    @classmethod
    def encode(cls, value: Union[list, tuple, dict, set]) -> bytes:
        """
        Encodes a dict, set, list or tuple into a key-value byte string.
        If a set, list or tuple is provided, the items are considered keys
        and added with empty values.

        Args:
            source_list (dict, set, list, tuple): list of strings

        Returns:
            bytes: Packed byte string containing the source data
        """
        cls.__validate_type__(value, True)

        if not isinstance(value, dict):
            value = {item: "" for item in value}

        list_data = b""

        for key, item in value.items():
            list_data += StringField.encode(key)

            item = (
                StringField.encode("")
                if item in ["", b""]
                else ListField.encode(
                    [item] if isinstance(item, (str, bytes)) else item
                )
            )

            list_data += item

        return BytestringField.encode(list_data)

    @staticmethod
    def decode(data: bytes) -> Tuple[dict, bytes]:
        """Decodes a list of strings from a block of bytes

        Args:
            data (bytes): The block of bytes containing a list of strings
        Returns:
            tuple: _description_
        """
        list_bytes, data = BytestringField.decode(data)

        decoded = {}
        while len(list_bytes) > 0:
            key, list_bytes = StringField.decode(list_bytes)
            value, list_bytes = BytestringField.decode(list_bytes)

            if value != b"":
                value = StringField.decode(value)[0]

            decoded[key] = value

        decoded = ensure_string(decoded)

        if "".join(decoded.values()) == "":
            return list(decoded.keys()), data

        return decoded, data

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        testvals = (
            self.value
            if not isinstance(self.value, dict)
            else list(self.value.keys()) + list(self.value.values())
        )

        if hasattr(self.value, "__iter__") and not all(
            (isinstance(val, (str, bytes)) for val in testvals)
        ):
            return _EX.InvalidFieldDataException(
                "Expected dict, list, tuple, set with string or byte keys and values"
            )

        return True

Ancestors

Subclasses

Class variables

var DATA_TYPE
var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[dict, bytes]

Decodes a list of strings from a block of bytes

Args

data : bytes
The block of bytes containing a list of strings

Returns

tuple
description
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[dict, bytes]:
    """Decodes a list of strings from a block of bytes

    Args:
        data (bytes): The block of bytes containing a list of strings
    Returns:
        tuple: _description_
    """
    list_bytes, data = BytestringField.decode(data)

    decoded = {}
    while len(list_bytes) > 0:
        key, list_bytes = StringField.decode(list_bytes)
        value, list_bytes = BytestringField.decode(list_bytes)

        if value != b"":
            value = StringField.decode(value)[0]

        decoded[key] = value

    decoded = ensure_string(decoded)

    if "".join(decoded.values()) == "":
        return list(decoded.keys()), data

    return decoded, data
def encode(value: Union[list, tuple, dict, set]) ‑> bytes

Encodes a dict, set, list or tuple into a key-value byte string. If a set, list or tuple is provided, the items are considered keys and added with empty values.

Args

source_list : dict, set, list, tuple
list of strings

Returns

bytes
Packed byte string containing the source data
Expand source code
@classmethod
def encode(cls, value: Union[list, tuple, dict, set]) -> bytes:
    """
    Encodes a dict, set, list or tuple into a key-value byte string.
    If a set, list or tuple is provided, the items are considered keys
    and added with empty values.

    Args:
        source_list (dict, set, list, tuple): list of strings

    Returns:
        bytes: Packed byte string containing the source data
    """
    cls.__validate_type__(value, True)

    if not isinstance(value, dict):
        value = {item: "" for item in value}

    list_data = b""

    for key, item in value.items():
        list_data += StringField.encode(key)

        item = (
            StringField.encode("")
            if item in ["", b""]
            else ListField.encode(
                [item] if isinstance(item, (str, bytes)) else item
            )
        )

        list_data += item

    return BytestringField.encode(list_data)

Inherited members

class ListField (value=None)

Certificate field representing a list or tuple of strings

Expand source code
class ListField(CertificateField):
    """
    Certificate field representing a list or tuple of strings
    """

    DATA_TYPE = (list, set, tuple)
    DEFAULT = []

    @classmethod
    def encode(cls, value: Union[list, tuple, set]) -> bytes:
        """Encodes a list or tuple to a byte string

        Args:
            source_list (list): list of strings
            null_separator (bool, optional): Insert blank string string between items. Default None

        Returns:
            bytes: Packed byte string containing the source data
        """
        cls.__validate_type__(value, True)

        try:
            if sum(not isinstance(item, (str, bytes)) for item in value) > 0:
                raise TypeError
        except TypeError:
            raise _EX.InvalidFieldDataException(
                "Expected list or tuple containing strings or bytes"
            ) from TypeError

        return BytestringField.encode(b"".join([StringField.encode(x) for x in value]))

    @staticmethod
    def decode(data: bytes) -> Tuple[list, bytes]:
        """Decodes a list of strings from a block of bytes

        Args:
            data (bytes): The block of bytes containing a list of strings
        Returns:
            tuple: _description_
        """
        list_bytes, data = BytestringField.decode(data)

        decoded = []
        while len(list_bytes) > 0:
            elem, list_bytes = StringField.decode(list_bytes)
            decoded.append(elem)

        return ensure_string(decoded), data

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        if hasattr(self.value, "__iter__") and not all(
            (isinstance(val, (str, bytes)) for val in self.value)
        ):
            return _EX.InvalidFieldDataException(
                "Expected list or tuple containing strings or bytes"
            )
        return True

Ancestors

Subclasses

Class variables

var DATA_TYPE
var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[list, bytes]

Decodes a list of strings from a block of bytes

Args

data : bytes
The block of bytes containing a list of strings

Returns

tuple
description
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[list, bytes]:
    """Decodes a list of strings from a block of bytes

    Args:
        data (bytes): The block of bytes containing a list of strings
    Returns:
        tuple: _description_
    """
    list_bytes, data = BytestringField.decode(data)

    decoded = []
    while len(list_bytes) > 0:
        elem, list_bytes = StringField.decode(list_bytes)
        decoded.append(elem)

    return ensure_string(decoded), data
def encode(value: Union[list, tuple, set]) ‑> bytes

Encodes a list or tuple to a byte string

Args

source_list : list
list of strings
null_separator : bool, optional
Insert blank string string between items. Default None

Returns

bytes
Packed byte string containing the source data
Expand source code
@classmethod
def encode(cls, value: Union[list, tuple, set]) -> bytes:
    """Encodes a list or tuple to a byte string

    Args:
        source_list (list): list of strings
        null_separator (bool, optional): Insert blank string string between items. Default None

    Returns:
        bytes: Packed byte string containing the source data
    """
    cls.__validate_type__(value, True)

    try:
        if sum(not isinstance(item, (str, bytes)) for item in value) > 0:
            raise TypeError
    except TypeError:
        raise _EX.InvalidFieldDataException(
            "Expected list or tuple containing strings or bytes"
        ) from TypeError

    return BytestringField.encode(b"".join([StringField.encode(x) for x in value]))

Inherited members

class MpIntegerField (value=None)

Certificate field representing a multiple precision integer, an integer too large to fit in 64 bits.

Expand source code
class MpIntegerField(BytestringField):
    """
    Certificate field representing a multiple precision integer,
    an integer too large to fit in 64 bits.
    """

    DATA_TYPE = int
    DEFAULT = 0

    @classmethod
    def encode(cls, value: int) -> bytes:
        """
        Encodes a multiprecision integer (integer larger than 64bit)
        into a packed byte string

        Args:
            value (int): Large integer

        Returns:
            bytes: Packed byte string containing integer
        """
        cls.__validate_type__(value, True)
        return BytestringField.encode(long_to_bytes(value))

    @staticmethod
    def decode(data: bytes) -> Tuple[int, bytes]:
        """Decodes a multiprecision integer (integer larger than 64bit)

        Args:
            data (bytes): Block of bytes containing a long (mp) integer

        Returns:
            tuple: Tuple with integer and remainder of data
        """
        mpint, data = BytestringField.decode(data)
        return bytes_to_long(mpint), data

Ancestors

Class variables

var DATA_TYPE

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.

>>> int('0b100', base=0)
4
var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[int, bytes]

Decodes a multiprecision integer (integer larger than 64bit)

Args

data : bytes
Block of bytes containing a long (mp) integer

Returns

tuple
Tuple with integer and remainder of data
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[int, bytes]:
    """Decodes a multiprecision integer (integer larger than 64bit)

    Args:
        data (bytes): Block of bytes containing a long (mp) integer

    Returns:
        tuple: Tuple with integer and remainder of data
    """
    mpint, data = BytestringField.decode(data)
    return bytes_to_long(mpint), data
def encode(value: int) ‑> bytes

Encodes a multiprecision integer (integer larger than 64bit) into a packed byte string

Args

value : int
Large integer

Returns

bytes
Packed byte string containing integer
Expand source code
@classmethod
def encode(cls, value: int) -> bytes:
    """
    Encodes a multiprecision integer (integer larger than 64bit)
    into a packed byte string

    Args:
        value (int): Large integer

    Returns:
        bytes: Packed byte string containing integer
    """
    cls.__validate_type__(value, True)
    return BytestringField.encode(long_to_bytes(value))

Inherited members

class NonceField (value=None)

Contains the nonce for the certificate, randomly generated this protects the integrity of the private key, especially for ecdsa.

Expand source code
class NonceField(BytestringField):
    """
    Contains the nonce for the certificate, randomly generated
    this protects the integrity of the private key, especially
    for ecdsa.
    """

    DEFAULT = generate_secure_nonce
    DATA_TYPE = (str, bytes)

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        if hasattr(self.value, "__count__") and len(self.value) < 32:
            return _EX.InvalidFieldDataException(
                "Expected a nonce of at least 32 bytes"
            )

        return True

Ancestors

Class variables

var DATA_TYPE

Methods

def DEFAULT(length: int = 128)

Generates a secure random nonce of the specified length. Mainly important for ECDSA keys, but is used with all key/certificate types https://blog.trailofbits.com/2020/06/11/ecdsa-handle-with-care/ https://datatracker.ietf.org/doc/html/rfc6979

Args

length : int, optional
Length of the nonce. Defaults to 64.

Returns

str
Nonce of the specified length
Expand source code
def generate_secure_nonce(length: int = 128):
    """Generates a secure random nonce of the specified length.
        Mainly important for ECDSA keys, but is used with all key/certificate types
        https://blog.trailofbits.com/2020/06/11/ecdsa-handle-with-care/
        https://datatracker.ietf.org/doc/html/rfc6979
    Args:
        length (int, optional): Length of the nonce. Defaults to 64.

    Returns:
        str: Nonce of the specified length
    """
    return str(randbits(length))

Inherited members

class PrincipalsField (value=None)

Contains a list of principals for the certificate, e.g. SERVERHOSTNAME01 or all-web-servers. If no principals are added, the certificate is valid only for servers that have no allowed principals specified

Expand source code
class PrincipalsField(ListField):
    """
    Contains a list of principals for the certificate,
    e.g. SERVERHOSTNAME01 or all-web-servers.
    If no principals are added, the certificate is valid
    only for servers that have no allowed principals specified
    """

    DEFAFULT = []
    DATA_TYPE = (list, set, tuple)

Ancestors

Class variables

var DATA_TYPE
var DEFAFULT

Inherited members

class PubkeyTypeField (value=None)

Contains the certificate type, which is based on the public key type the certificate is created for, e.g. 'ssh-ed25519-cert-v01@openssh.com' for an ED25519 key

Expand source code
class PubkeyTypeField(StringField):
    """
    Contains the certificate type, which is based on the
    public key type the certificate is created for, e.g.
    'ssh-ed25519-cert-v01@openssh.com' for an ED25519 key
    """

    DEFAULT = None
    DATA_TYPE = (str, bytes)
    ALLOWED_VALUES = (
        "ssh-rsa-cert-v01@openssh.com",
        "rsa-sha2-256-cert-v01@openssh.com",
        "rsa-sha2-512-cert-v01@openssh.com",
        "ssh-dss-cert-v01@openssh.com",
        "ecdsa-sha2-nistp256-cert-v01@openssh.com",
        "ecdsa-sha2-nistp384-cert-v01@openssh.com",
        "ecdsa-sha2-nistp521-cert-v01@openssh.com",
        "ssh-ed25519-cert-v01@openssh.com",
    )

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        if ensure_string(self.value) not in self.ALLOWED_VALUES:
            return _EX.InvalidFieldDataException(
                "Expected one of the following values: "
                + NEWLINE.join(self.ALLOWED_VALUES)
            )

        return True

Ancestors

Class variables

var ALLOWED_VALUES
var DATA_TYPE
var DEFAULT

Inherited members

class PublicKeyField (value=None)

Contains the subject (User or Host) public key for whom/which the certificate is created.

Expand source code
class PublicKeyField(CertificateField):
    """
    Contains the subject (User or Host) public key for whom/which
    the certificate is created.
    """

    DEFAULT = None
    DATA_TYPE = PublicKey

    def __table__(self) -> tuple:
        return [str(self.name), str(self.value.get_fingerprint())]

    def __str__(self) -> str:
        return " ".join(
            [
                self.__class__.__name__.replace("PubkeyField", ""),
                self.value.get_fingerprint(),
            ]
        )

    @classmethod
    def encode(cls, value: PublicKey) -> bytes:
        """
        Encode the certificate field to a byte string

        Args:
            value (RsaPublicKey): The public key to encode

        Returns:
            bytes: A byte string with the encoded public key
        """
        cls.__validate_type__(value, True)
        return BytestringField.decode(value.raw_bytes())[1]

    @staticmethod
    def from_object(public_key: PublicKey):
        """
        Loads the public key from a sshkey_tools.keys.PublicKey
        class or childclass

        Args:
            public_key (PublicKey): The public key for which to
                                    create the certificate

        Raises:
            _EX.InvalidKeyException: Invalid public key

        Returns:
            PublicKeyField: A child class of PublicKeyField specific
                            to the chosen public key
        """
        try:
            return globals()[SUBJECT_PUBKEY_MAP[public_key.__class__]](value=public_key)
        except KeyError:
            raise _EX.InvalidKeyException("The public key is invalid") from KeyError

Ancestors

Subclasses

Class variables

var DATA_TYPE

Class for handling SSH public keys

var DEFAULT

Static methods

def encode(value: PublicKey) ‑> bytes

Encode the certificate field to a byte string

Args

value : RsaPublicKey
The public key to encode

Returns

bytes
A byte string with the encoded public key
Expand source code
@classmethod
def encode(cls, value: PublicKey) -> bytes:
    """
    Encode the certificate field to a byte string

    Args:
        value (RsaPublicKey): The public key to encode

    Returns:
        bytes: A byte string with the encoded public key
    """
    cls.__validate_type__(value, True)
    return BytestringField.decode(value.raw_bytes())[1]
def from_object(public_key: PublicKey)

Loads the public key from a sshkey_tools.keys.PublicKey class or childclass

Args

public_key : PublicKey
The public key for which to create the certificate

Raises

_EX.InvalidKeyException
Invalid public key

Returns

PublicKeyField
A child class of PublicKeyField specific to the chosen public key
Expand source code
@staticmethod
def from_object(public_key: PublicKey):
    """
    Loads the public key from a sshkey_tools.keys.PublicKey
    class or childclass

    Args:
        public_key (PublicKey): The public key for which to
                                create the certificate

    Raises:
        _EX.InvalidKeyException: Invalid public key

    Returns:
        PublicKeyField: A child class of PublicKeyField specific
                        to the chosen public key
    """
    try:
        return globals()[SUBJECT_PUBKEY_MAP[public_key.__class__]](value=public_key)
    except KeyError:
        raise _EX.InvalidKeyException("The public key is invalid") from KeyError

Inherited members

class ReservedField (value=None)

This field is reserved for future use, and doesn't contain any actual data, just an empty string.

Expand source code
class ReservedField(StringField):
    """
    This field is reserved for future use, and
    doesn't contain any actual data, just an empty string.
    """

    DEFAULT = ""
    DATA_TYPE = str

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        return (
            True
            if self.value == ""
            else _EX.InvalidDataException("The reserved field is not empty")
        )

Ancestors

Class variables

var DATA_TYPE

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

var DEFAULT

Inherited members

class RsaPubkeyField (value=None)

Holds the RSA Public Key for RSA Certificates

Expand source code
class RsaPubkeyField(PublicKeyField):
    """
    Holds the RSA Public Key for RSA Certificates
    """

    DEFAULT = None
    DATA_TYPE = RsaPublicKey

    @staticmethod
    def decode(data: bytes) -> Tuple[RsaPublicKey, bytes]:
        """
        Decode the certificate field from a byte string
        starting with the encoded public key

        Args:
            data (bytes): The byte string starting with the encoded key

        Returns:
            Tuple[RsaPublicKey, bytes]: The PublicKey field and remainder of the data
        """
        e, data = MpIntegerField.decode(data)
        n, data = MpIntegerField.decode(data)

        return RsaPublicKey.from_numbers(e=e, n=n), data

Ancestors

Class variables

var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[RsaPublicKey, bytes]

Decode the certificate field from a byte string starting with the encoded public key

Args

data : bytes
The byte string starting with the encoded key

Returns

Tuple[RsaPublicKey, bytes]
The PublicKey field and remainder of the data
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[RsaPublicKey, bytes]:
    """
    Decode the certificate field from a byte string
    starting with the encoded public key

    Args:
        data (bytes): The byte string starting with the encoded key

    Returns:
        Tuple[RsaPublicKey, bytes]: The PublicKey field and remainder of the data
    """
    e, data = MpIntegerField.decode(data)
    n, data = MpIntegerField.decode(data)

    return RsaPublicKey.from_numbers(e=e, n=n), data

Inherited members

class RsaSignatureField (private_key: RsaPrivateKey = None, hash_alg: RsaAlgs = RsaAlgs.SHA512, signature: bytes = None)

Creates and contains the RSA signature from an RSA Private Key

Expand source code
class RsaSignatureField(SignatureField):
    """
    Creates and contains the RSA signature from an RSA Private Key
    """

    DEFAULT = None
    DATA_TYPE = bytes

    def __init__(
        self,
        private_key: RsaPrivateKey = None,
        hash_alg: RsaAlgs = RsaAlgs.SHA512,
        signature: bytes = None,
    ):
        super().__init__(private_key, signature)
        self.hash_alg = hash_alg

    @classmethod
    # pylint: disable=arguments-renamed
    def encode(cls, value: bytes, hash_alg: RsaAlgs = RsaAlgs.SHA512) -> bytes:
        """
        Encodes the value to a byte string

        Args:
            signature (bytes): The signature bytes to encode
            hash_alg (RsaAlgs, optional):  The hash algorithm used for the signature.
                                            Defaults to RsaAlgs.SHA256.

        Returns:
            bytes: The encoded byte string
        """
        cls.__validate_type__(value, True)

        return BytestringField.encode(
            StringField.encode(hash_alg.value[0]) + BytestringField.encode(value)
        )

    @staticmethod
    def decode(data: bytes) -> Tuple[Tuple[bytes, bytes], bytes]:
        """
        Decodes a bytestring containing a signature

        Args:
            data (bytes): The bytestring starting with the RSA Signature

        Returns:
            Tuple[ Tuple[ bytes, bytes ], bytes ]: (signature_type, signature), remainder of data
        """
        signature, data = BytestringField.decode(data)

        sig_type, signature = StringField.decode(signature)
        signature, _ = BytestringField.decode(signature)

        return (sig_type, signature), data

    @classmethod
    def from_decode(cls, data: bytes) -> Tuple["RsaSignatureField", bytes]:
        """
        Generates an RsaSignatureField class from the encoded signature

        Args:
            data (bytes): The bytestring containing the encoded signature

        Raises:
            _EX.InvalidDataException: Invalid data

        Returns:
            Tuple[RsaSignatureField, bytes]: RSA Signature field and remainder of data
        """
        signature, data = cls.decode(data)

        return (
            cls(
                private_key=None,
                hash_alg=[alg for alg in RsaAlgs if alg.value[0] == signature[0]][0],
                signature=signature[1],
            ),
            data,
        )

    # pylint: disable=unused-argument
    def sign(self, data: bytes, hash_alg: RsaAlgs = RsaAlgs.SHA512, **kwargs) -> None:
        """
        Signs the provided data with the provided private key

        Args:
            data (bytes): The data to be signed
            hash_alg (RsaAlgs, optional): The RSA algorithm to use for hashing.
                                           Defaults to RsaAlgs.SHA256.
        """
        self.value = self.private_key.sign(data, hash_alg)

        self.hash_alg = hash_alg
        self.is_signed = True

    def __bytes__(self):
        return self.encode(self.value, self.hash_alg)

Ancestors

Class variables

var DEFAULT

Static methods

def decode(data: bytes) ‑> Tuple[Tuple[bytes, bytes], bytes]

Decodes a bytestring containing a signature

Args

data : bytes
The bytestring starting with the RSA Signature

Returns

Tuple[ Tuple[ bytes, bytes ], bytes ]
(signature_type, signature), remainder of data
Expand source code
@staticmethod
def decode(data: bytes) -> Tuple[Tuple[bytes, bytes], bytes]:
    """
    Decodes a bytestring containing a signature

    Args:
        data (bytes): The bytestring starting with the RSA Signature

    Returns:
        Tuple[ Tuple[ bytes, bytes ], bytes ]: (signature_type, signature), remainder of data
    """
    signature, data = BytestringField.decode(data)

    sig_type, signature = StringField.decode(signature)
    signature, _ = BytestringField.decode(signature)

    return (sig_type, signature), data
def encode(value: bytes, hash_alg: RsaAlgs = RsaAlgs.SHA512) ‑> bytes

Encodes the value to a byte string

Args

signature : bytes
The signature bytes to encode
hash_alg : RsaAlgs, optional
The hash algorithm used for the signature. Defaults to RsaAlgs.SHA256.

Returns

bytes
The encoded byte string
Expand source code
@classmethod
# pylint: disable=arguments-renamed
def encode(cls, value: bytes, hash_alg: RsaAlgs = RsaAlgs.SHA512) -> bytes:
    """
    Encodes the value to a byte string

    Args:
        signature (bytes): The signature bytes to encode
        hash_alg (RsaAlgs, optional):  The hash algorithm used for the signature.
                                        Defaults to RsaAlgs.SHA256.

    Returns:
        bytes: The encoded byte string
    """
    cls.__validate_type__(value, True)

    return BytestringField.encode(
        StringField.encode(hash_alg.value[0]) + BytestringField.encode(value)
    )
def from_decode(data: bytes) ‑> Tuple[RsaSignatureField, bytes]

Generates an RsaSignatureField class from the encoded signature

Args

data : bytes
The bytestring containing the encoded signature

Raises

_EX.InvalidDataException
Invalid data

Returns

Tuple[RsaSignatureField, bytes]
RSA Signature field and remainder of data
Expand source code
@classmethod
def from_decode(cls, data: bytes) -> Tuple["RsaSignatureField", bytes]:
    """
    Generates an RsaSignatureField class from the encoded signature

    Args:
        data (bytes): The bytestring containing the encoded signature

    Raises:
        _EX.InvalidDataException: Invalid data

    Returns:
        Tuple[RsaSignatureField, bytes]: RSA Signature field and remainder of data
    """
    signature, data = cls.decode(data)

    return (
        cls(
            private_key=None,
            hash_alg=[alg for alg in RsaAlgs if alg.value[0] == signature[0]][0],
            signature=signature[1],
        ),
        data,
    )

Methods

def sign(self, data: bytes, hash_alg: RsaAlgs = RsaAlgs.SHA512, **kwargs) ‑> None

Signs the provided data with the provided private key

Args

data : bytes
The data to be signed
hash_alg : RsaAlgs, optional
The RSA algorithm to use for hashing. Defaults to RsaAlgs.SHA256.
Expand source code
def sign(self, data: bytes, hash_alg: RsaAlgs = RsaAlgs.SHA512, **kwargs) -> None:
    """
    Signs the provided data with the provided private key

    Args:
        data (bytes): The data to be signed
        hash_alg (RsaAlgs, optional): The RSA algorithm to use for hashing.
                                       Defaults to RsaAlgs.SHA256.
    """
    self.value = self.private_key.sign(data, hash_alg)

    self.hash_alg = hash_alg
    self.is_signed = True

Inherited members

class SerialField (value=None)

Contains the numeric serial number of the certificate, maximum is (2**64)-1

Expand source code
class SerialField(Integer64Field):
    """
    Contains the numeric serial number of the certificate,
    maximum is (2**64)-1
    """

    DEFAULT = random_serial
    DATA_TYPE = int

Ancestors

Methods

def DEFAULT() ‑> str

Generates a random serial number

Returns

int
Random serial
Expand source code
def random_serial() -> str:
    """Generates a random serial number

    Returns:
        int: Random serial
    """
    return randint(0, 2**64 - 1)

Inherited members

class SignatureField (private_key: PrivateKey = None, signature: bytes = None)

Creates and contains the signature of the certificate

Expand source code
class SignatureField(CertificateField):
    """
    Creates and contains the signature of the certificate
    """

    DEFAULT = None
    DATA_TYPE = bytes

    # pylint: disable=super-init-not-called
    def __init__(self, private_key: PrivateKey = None, signature: bytes = None):
        self.private_key = private_key
        self.is_signed = False
        self.value = signature

        if signature is not None and ensure_bytestring(signature) not in ("", " "):
            self.is_signed = True

    def __table__(self) -> tuple:
        msg = "No signature"
        if self.is_signed and self.private_key is not None:
            msg = f"Signed with private key {self.private_key.get_fingerprint()}"
        
        if self.is_signed and self.private_key is None:
            msg = "Signed with: See pubkey fingerprint above"

        return ("Signature", msg)

    @staticmethod
    def from_object(private_key: PrivateKey):
        """
        Load a private key from a PrivateKey object

        Args:
            private_key (PrivateKey): Private key to use for signing

        Raises:
            _EX.InvalidKeyException: Invalid private key

        Returns:
            SignatureField: SignatureField child class
        """
        try:
            return globals()[CA_SIGNATURE_MAP[private_key.__class__]](
                private_key=private_key
            )
        except KeyError:
            raise _EX.InvalidKeyException(
                "The private key provided is invalid or not supported"
            ) from KeyError

    @staticmethod
    def from_decode(data: bytes) -> Tuple["SignatureField", bytes]:
        """
        Generates a SignatureField child class from the encoded signature

        Args:
            data (bytes): The bytestring containing the encoded signature

        Raises:
            _EX.InvalidDataException: Invalid data

        Returns:
            SignatureField: child of SignatureField
        """
        signature, _ = BytestringField.decode(data)
        signature_type = BytestringField.decode(signature)[0]

        for key, value in SIGNATURE_TYPE_MAP.items():
            if key in signature_type:
                return globals()[value].from_decode(data)

        raise _EX.InvalidDataException("No matching signature type found")

    def can_sign(self):
        """
        Determines if a signature can be generated from
        this private key
        """
        return self.private_key is not None

    def sign(self, data: bytes, **kwargs) -> None:
        """
        Placeholder signing function
        """
        raise _EX.InvalidClassCallException("The base class has no sign function")

    def __bytes__(self) -> None:
        return self.encode(self.value)

Ancestors

Subclasses

Class variables

var DATA_TYPE

bytes(iterable_of_ints) -> bytes bytes(string, encoding[, errors]) -> bytes bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer bytes(int) -> bytes object of size given by the parameter initialized with null bytes bytes() -> empty bytes object

Construct an immutable array of bytes from: - an iterable yielding integers in range(256) - a text string encoded using the specified encoding - any object implementing the buffer API. - an integer

var DEFAULT

Static methods

def from_decode(data: bytes) ‑> Tuple[SignatureField, bytes]

Generates a SignatureField child class from the encoded signature

Args

data : bytes
The bytestring containing the encoded signature

Raises

_EX.InvalidDataException
Invalid data

Returns

SignatureField
child of SignatureField
Expand source code
@staticmethod
def from_decode(data: bytes) -> Tuple["SignatureField", bytes]:
    """
    Generates a SignatureField child class from the encoded signature

    Args:
        data (bytes): The bytestring containing the encoded signature

    Raises:
        _EX.InvalidDataException: Invalid data

    Returns:
        SignatureField: child of SignatureField
    """
    signature, _ = BytestringField.decode(data)
    signature_type = BytestringField.decode(signature)[0]

    for key, value in SIGNATURE_TYPE_MAP.items():
        if key in signature_type:
            return globals()[value].from_decode(data)

    raise _EX.InvalidDataException("No matching signature type found")
def from_object(private_key: PrivateKey)

Load a private key from a PrivateKey object

Args

private_key : PrivateKey
Private key to use for signing

Raises

_EX.InvalidKeyException
Invalid private key

Returns

SignatureField
SignatureField child class
Expand source code
@staticmethod
def from_object(private_key: PrivateKey):
    """
    Load a private key from a PrivateKey object

    Args:
        private_key (PrivateKey): Private key to use for signing

    Raises:
        _EX.InvalidKeyException: Invalid private key

    Returns:
        SignatureField: SignatureField child class
    """
    try:
        return globals()[CA_SIGNATURE_MAP[private_key.__class__]](
            private_key=private_key
        )
    except KeyError:
        raise _EX.InvalidKeyException(
            "The private key provided is invalid or not supported"
        ) from KeyError

Methods

def can_sign(self)

Determines if a signature can be generated from this private key

Expand source code
def can_sign(self):
    """
    Determines if a signature can be generated from
    this private key
    """
    return self.private_key is not None
def sign(self, data: bytes, **kwargs) ‑> None

Placeholder signing function

Expand source code
def sign(self, data: bytes, **kwargs) -> None:
    """
    Placeholder signing function
    """
    raise _EX.InvalidClassCallException("The base class has no sign function")

Inherited members

class StringField (value=None)

Field representing a string value

Expand source code
class StringField(BytestringField):
    """
    Field representing a string value
    """

    DATA_TYPE = (str, bytes)
    DEFAULT = ""

    @classmethod
    def encode(cls, value: str, encoding: str = "utf-8"):
        """
        Encodes a string or bytestring into a packed byte string

        Args:
            value (Union[str, bytes]): The string/bytestring to encode
            encoding (str): The encoding to user for the string

        Returns:
            bytes: Packed byte string containing the source data
        """
        return super().encode(value, encoding)

    @staticmethod
    def decode(data: bytes, encoding: str = "utf-8") -> Tuple[str, bytes]:
        """
        Unpacks the next string from a packed byte string

        Args:
            data (bytes): The packed byte string to unpack

        Returns:
            tuple(bytes, bytes):  The next block of bytes from the packed byte
                                  string and remainder of the data
        """
        return BytestringField.decode(data, encoding)

Ancestors

Subclasses

Class variables

var DATA_TYPE
var DEFAULT

Static methods

def encode(value: str, encoding: str = 'utf-8')

Encodes a string or bytestring into a packed byte string

Args

value : Union[str, bytes]
The string/bytestring to encode
encoding : str
The encoding to user for the string

Returns

bytes
Packed byte string containing the source data
Expand source code
@classmethod
def encode(cls, value: str, encoding: str = "utf-8"):
    """
    Encodes a string or bytestring into a packed byte string

    Args:
        value (Union[str, bytes]): The string/bytestring to encode
        encoding (str): The encoding to user for the string

    Returns:
        bytes: Packed byte string containing the source data
    """
    return super().encode(value, encoding)

Inherited members

class ValidAfterField (value=None)

Contains the start of the validity period for the certificate, represented by a datetime object

Expand source code
class ValidAfterField(DateTimeField):
    """
    Contains the start of the validity period for the certificate,
    represented by a datetime object
    """

    DEFAULT = datetime.now()
    DATA_TYPE = (datetime, int)

Ancestors

Inherited members

class ValidBeforeField (value=None)

Contains the end of the validity period for the certificate, represented by a datetime object

Expand source code
class ValidBeforeField(DateTimeField):
    """
    Contains the end of the validity period for the certificate,
    represented by a datetime object
    """

    DEFAULT = datetime.now() + timedelta(minutes=10)
    DATA_TYPE = (datetime, int)

    def __validate_value__(self) -> Union[bool, Exception]:
        """
        Validates the contents of the field
        Additional checks over standard datetime field are
        done to ensure no already expired certificates are
        created
        """
        if isinstance(self.__validate_type__(self.value), Exception):
            return _EX.InvalidFieldDataException(
                f"{self.get_name()} Could not validate value, invalid type"
            )

        super().__validate_value__()
        check = (
            self.value
            if isinstance(self.value, datetime)
            else datetime.fromtimestamp(self.value)
        )

        if check < datetime.now():
            return _EX.InvalidCertificateFieldException(
                "The certificate validity period is invalid"
                + " (expected a future datetime object or timestamp)"
            )

        return True

Ancestors

Inherited members