Skip to content

Commit

Permalink
Move CertificateBundle into Sapphire
Browse files Browse the repository at this point in the history
  • Loading branch information
tysmith committed May 28, 2024
1 parent 19e518a commit 9981624
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 139 deletions.
118 changes: 2 additions & 116 deletions grizzly/common/utils.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from datetime import datetime, timedelta, timezone
from enum import IntEnum, unique
from importlib.metadata import PackageNotFoundError, version
from ipaddress import IPv4Address
from logging import DEBUG, basicConfig, getLogger
from os import getenv, getpid
from os import getenv
from pathlib import Path
from shutil import rmtree
from tempfile import gettempdir
from typing import Any, Dict, Iterable, Optional, Tuple, Union

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key
from cryptography.x509.oid import NameOID
from typing import Any, Iterable, Optional, Tuple, Union

__all__ = (
"CertificateBundle",
"ConfigError",
"configure_logging",
"display_time_limits",
"DEFAULT_TIME_LIMIT",
"Exit",
"generate_certificates",
"grz_tmp",
"HARNESS_FILE",
"time_limits",
Expand All @@ -48,43 +37,6 @@
TIMEOUT_DELAY = 15


class CertificateBundle:
"""Contains root CA, host CA and private key files."""

def __init__(self, path: Path, root: Path, host: Path, key: Path) -> None:
self._base = path
self.root = root
self.host = host
self.key = key

@classmethod
def create(cls, path: Optional[Path] = None) -> "CertificateBundle":
"""Create certificate files.
Args:
path: Location to store generated files.
Returns:
CertificateBundle
"""
if path is None:
path = grz_tmp("certs", str(getpid()))
certs = generate_certificates(path)
return cls(path, certs["root"], certs["host"], certs["key"])

def cleanup(self) -> None:
"""Remove certificate files.
Args:
None
Returns:
None
"""
LOG.debug("removing certificate path (%s)", self._base)
rmtree(self._base, ignore_errors=True)


class ConfigError(Exception):
"""Raised to indicate invalid configuration a state"""

Expand Down Expand Up @@ -171,72 +123,6 @@ def grz_tmp(*subdir: Union[str, Path]) -> Path:
return path


def generate_certificates(cert_dir: Path) -> Dict[str, Path]:
"""Generate a root CA and host certificate.
Taken from https://stackoverflow.com/a/56292132
"""
root_key = generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend(),
)
root_sub = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Grizzly Root CA")])
root_cert = (
x509.CertificateBuilder()
.subject_name(root_sub)
.issuer_name(root_sub)
.public_key(root_key.public_key())
.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now(timezone.utc))
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=30))
.sign(root_key, hashes.SHA256(), default_backend())
)

# Now we want to generate a cert from that root
host_key = generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend(),
)
host_sub = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Grizzly Test Cert")])
host_cert = (
x509.CertificateBuilder()
.subject_name(host_sub)
.issuer_name(root_cert.issuer)
.public_key(host_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now(timezone.utc))
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=30))
.add_extension(
x509.SubjectAlternativeName(
[
x509.DNSName("localhost"),
x509.IPAddress(IPv4Address("127.0.0.1")),
]
),
critical=False,
)
.sign(root_key, hashes.SHA256(), default_backend())
)

root_file = cert_dir / "root.pem"
root_file.write_bytes(root_cert.public_bytes(serialization.Encoding.PEM))
cert_file = cert_dir / "host.pem"
cert_file.write_bytes(host_cert.public_bytes(serialization.Encoding.PEM))
key_file = cert_dir / "host.key"
key_file.write_bytes(
host_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
)

return {"root": root_file, "host": cert_file, "key": key_file}


def time_limits(
time_limit: Optional[int],
timeout: Optional[int],
Expand Down
10 changes: 2 additions & 8 deletions grizzly/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from os import getpid
from typing import Optional, cast

from sapphire import Sapphire
from sapphire import CertificateBundle, Sapphire

from .adapter import Adapter
from .common.plugins import load as load_plugin
Expand All @@ -16,13 +16,7 @@
FuzzManagerReporter,
Reporter,
)
from .common.utils import (
CertificateBundle,
Exit,
configure_logging,
display_time_limits,
time_limits,
)
from .common.utils import Exit, configure_logging, display_time_limits, time_limits
from .session import LogRate, Session
from .target import Target, TargetLaunchError, TargetLaunchTimeout

Expand Down
10 changes: 2 additions & 8 deletions grizzly/reduce/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from FTB.Signatures.CrashInfo import CrashSignature

from sapphire import Sapphire
from sapphire import CertificateBundle, Sapphire

from ..common.fuzzmanager import CrashEntry
from ..common.plugins import load as load_plugin
Expand All @@ -28,13 +28,7 @@
from ..common.status import STATUS_DB_REDUCE, ReductionStatus
from ..common.status_reporter import ReductionStatusReporter
from ..common.storage import TestCase, TestCaseLoadFailure
from ..common.utils import (
CertificateBundle,
ConfigError,
Exit,
configure_logging,
time_limits,
)
from ..common.utils import ConfigError, Exit, configure_logging, time_limits
from ..replay import ReplayManager, ReplayResult
from ..target import AssetManager, Target, TargetLaunchError, TargetLaunchTimeout
from .exceptions import GrizzlyReduceBaseException, NotReproducible
Expand Down
3 changes: 1 addition & 2 deletions grizzly/replay/replay.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from FTB.Signatures.CrashInfo import CrashSignature

from sapphire import Sapphire, ServerMap
from sapphire import CertificateBundle, Sapphire, ServerMap

from ..common.plugins import load as load_plugin
from ..common.report import Report
Expand All @@ -25,7 +25,6 @@
from ..common.storage import TestCase, TestCaseLoadFailure
from ..common.utils import (
HARNESS_FILE,
CertificateBundle,
ConfigError,
Exit,
configure_logging,
Expand Down
4 changes: 3 additions & 1 deletion grizzly/target/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
from threading import Lock
from typing import Any, Dict, Optional, Set, Tuple, final

from sapphire import CertificateBundle

from ..common.report import Report
from ..common.utils import CertificateBundle, grz_tmp
from ..common.utils import grz_tmp
from .assets import AssetManager
from .target_monitor import TargetMonitor

Expand Down
3 changes: 2 additions & 1 deletion grizzly/target/test_puppet_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from ffpuppet import BrowserTerminatedError, BrowserTimeoutError, Debugger, Reason
from pytest import mark, raises

from ..common.utils import CertificateBundle
from sapphire import CertificateBundle

from .assets import AssetManager
from .puppet_target import PuppetTarget
from .target import Result, TargetLaunchError, TargetLaunchTimeout
Expand Down
2 changes: 2 additions & 0 deletions sapphire/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

from .certificate_bundle import CertificateBundle
from .core import Sapphire
from .job import Served
from .server_map import ServerMap

__all__ = (
"CertificateBundle",
"Sapphire",
"Served",
"ServerMap",
Expand Down
122 changes: 122 additions & 0 deletions sapphire/certificate_bundle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from datetime import datetime, timedelta, timezone
from ipaddress import IPv4Address
from logging import getLogger
from os import getpid
from pathlib import Path
from shutil import rmtree
from tempfile import mkdtemp
from typing import Dict, Optional

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key
from cryptography.x509.oid import NameOID

LOG = getLogger(__name__)


def generate_certificates(cert_dir: Path) -> Dict[str, Path]:
"""Generate a root CA and host certificate.
Credit to https://stackoverflow.com/a/56292132
"""
root_key = generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend(),
)
root_sub = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Grizzly Root CA")])
root_cert = (
x509.CertificateBuilder()
.subject_name(root_sub)
.issuer_name(root_sub)
.public_key(root_key.public_key())
.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now(timezone.utc))
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=30))
.sign(root_key, hashes.SHA256(), default_backend())
)

# Now we want to generate a cert from that root
host_key = generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend(),
)
host_sub = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Grizzly Test Cert")])
host_cert = (
x509.CertificateBuilder()
.subject_name(host_sub)
.issuer_name(root_cert.issuer)
.public_key(host_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now(timezone.utc))
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=30))
.add_extension(
x509.SubjectAlternativeName(
[
x509.DNSName("localhost"),
x509.IPAddress(IPv4Address("127.0.0.1")),
]
),
critical=False,
)
.sign(root_key, hashes.SHA256(), default_backend())
)

root_file = cert_dir / "root.pem"
root_file.write_bytes(root_cert.public_bytes(serialization.Encoding.PEM))
cert_file = cert_dir / "host.pem"
cert_file.write_bytes(host_cert.public_bytes(serialization.Encoding.PEM))
key_file = cert_dir / "host.key"
key_file.write_bytes(
host_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
)

return {"root": root_file, "host": cert_file, "key": key_file}


class CertificateBundle:
"""Contains root CA, host CA and private key files."""

def __init__(self, path: Path, root: Path, host: Path, key: Path) -> None:
self._base = path
self.root = root
self.host = host
self.key = key

@classmethod
def create(cls, path: Optional[Path] = None) -> "CertificateBundle":
"""Create certificate files.
Args:
path: Location to store generated files.
Returns:
CertificateBundle
"""
if path is None:
path = Path(mkdtemp(prefix=f"sapphire_certs_{getpid()}_"))
certs = generate_certificates(path)
return cls(path, certs["root"], certs["host"], certs["key"])

def cleanup(self) -> None:
"""Remove certificate files.
Args:
None
Returns:
None
"""
LOG.debug("removing certificate path (%s)", self._base)
rmtree(self._base, ignore_errors=True)
3 changes: 2 additions & 1 deletion sapphire/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from time import sleep, time
from typing import Any, Callable, Dict, Iterable, Optional, Tuple, Union, cast

from .certificate_bundle import CertificateBundle
from .connection_manager import ConnectionManager
from .job import Job, Served
from .server_map import ServerMap
Expand Down Expand Up @@ -103,7 +104,7 @@ def __init__(
self,
allow_remote: bool = False,
auto_close: int = -1,
certs=None,
certs: Optional[CertificateBundle] = None,
max_workers: int = 10,
port: int = 0,
timeout: int = 60,
Expand Down
Loading

0 comments on commit 9981624

Please sign in to comment.