diff --git a/lib/charms/observability_libs/v0/cert_handler.py b/lib/charms/observability_libs/v0/cert_handler.py
index 88a8374e..db14e00f 100644
--- a/lib/charms/observability_libs/v0/cert_handler.py
+++ b/lib/charms/observability_libs/v0/cert_handler.py
@@ -64,7 +64,7 @@
 
 LIBID = "b5cd5cd580f3428fa5f59a8876dcbe6a"
 LIBAPI = 0
-LIBPATCH = 8
+LIBPATCH = 9
 
 
 def is_ip_address(value: str) -> bool:
@@ -181,33 +181,40 @@ def _peer_relation(self) -> Optional[Relation]:
         return self.charm.model.get_relation(self.peer_relation_name, None)
 
     def _on_peer_relation_created(self, _):
-        """Generate the private key and store it in a peer relation."""
-        # We're in "relation-created", so the relation should be there
+        """Generate the CSR if the certificates relation is ready."""
+        self._generate_privkey()
 
-        # Just in case we already have a private key, do not overwrite it.
-        # Not sure how this could happen.
-        # TODO figure out how to go about key rotation.
-        if not self._private_key:
-            private_key = generate_private_key()
-            self._private_key = private_key.decode()
-
-        # Generate CSR here, in case peer events fired after tls-certificate relation events
+        # check cert relation is ready
         if not (self.charm.model.get_relation(self.certificates_relation_name)):
             # peer relation event happened to fire before tls-certificates events.
             # Abort, and let the "certificates joined" observer create the CSR.
+            logger.info("certhandler waiting on certificates relation")
             return
 
+        logger.debug("certhandler has peer and certs relation: proceeding to generate csr")
         self._generate_csr()
 
     def _on_certificates_relation_joined(self, _) -> None:
-        """Generate the CSR and request the certificate creation."""
+        """Generate the CSR if the peer relation is ready."""
+        self._generate_privkey()
+
+        # check peer relation is there
         if not self._peer_relation:
             # tls-certificates relation event happened to fire before peer events.
             # Abort, and let the "peer joined" relation create the CSR.
+            logger.info("certhandler waiting on peer relation")
             return
 
+        logger.debug("certhandler has peer and certs relation: proceeding to generate csr")
         self._generate_csr()
 
+    def _generate_privkey(self):
+        # Generate priv key unless done already
+        # TODO figure out how to go about key rotation.
+        if not self._private_key:
+            private_key = generate_private_key()
+            self._private_key = private_key.decode()
+
     def _on_config_changed(self, _):
         # FIXME on config changed, the web_external_url may or may not change. But because every
         #  call to `generate_csr` appends a uuid, CSRs cannot be easily compared to one another.
@@ -237,7 +244,12 @@ def _generate_csr(
         # In case we already have a csr, do not overwrite it by default.
         if overwrite or renew or not self._csr:
             private_key = self._private_key
-            assert private_key is not None  # for type checker
+            if private_key is None:
+                # FIXME: raise this in a less nested scope by
+                #  generating privkey and csr in the same method.
+                raise RuntimeError(
+                    "private key unset. call _generate_privkey() before you call this method."
+                )
             csr = generate_csr(
                 private_key=private_key.encode(),
                 subject=self.cert_subject,
diff --git a/lib/charms/tls_certificates_interface/v2/tls_certificates.py b/lib/charms/tls_certificates_interface/v2/tls_certificates.py
index f4a08366..09a74434 100644
--- a/lib/charms/tls_certificates_interface/v2/tls_certificates.py
+++ b/lib/charms/tls_certificates_interface/v2/tls_certificates.py
@@ -287,7 +287,7 @@ def _on_all_certificates_invalidated(self, event: AllCertificatesInvalidatedEven
 from cryptography.hazmat.primitives.asymmetric import rsa
 from cryptography.hazmat.primitives.serialization import pkcs12
 from cryptography.x509.extensions import Extension, ExtensionNotFound
-from jsonschema import exceptions, validate  # type: ignore[import]
+from jsonschema import exceptions, validate  # type: ignore[import-untyped]
 from ops.charm import (
     CharmBase,
     CharmEvents,
@@ -308,7 +308,7 @@ def _on_all_certificates_invalidated(self, event: AllCertificatesInvalidatedEven
 
 # Increment this PATCH version before using `charmcraft publish-lib` or reset
 # to 0 if you are raising the major API version
-LIBPATCH = 16
+LIBPATCH = 19
 
 PYDEPS = ["cryptography", "jsonschema"]
 
@@ -335,7 +335,10 @@ def _on_all_certificates_invalidated(self, event: AllCertificatesInvalidatedEven
             "type": "array",
             "items": {
                 "type": "object",
-                "properties": {"certificate_signing_request": {"type": "string"}},
+                "properties": {
+                    "certificate_signing_request": {"type": "string"},
+                    "ca": {"type": "boolean"},
+                },
                 "required": ["certificate_signing_request"],
             },
         }
@@ -536,22 +539,31 @@ def restore(self, snapshot: dict):
 class CertificateCreationRequestEvent(EventBase):
     """Charm Event triggered when a TLS certificate is required."""
 
-    def __init__(self, handle: Handle, certificate_signing_request: str, relation_id: int):
+    def __init__(
+        self,
+        handle: Handle,
+        certificate_signing_request: str,
+        relation_id: int,
+        is_ca: bool = False,
+    ):
         super().__init__(handle)
         self.certificate_signing_request = certificate_signing_request
         self.relation_id = relation_id
+        self.is_ca = is_ca
 
     def snapshot(self) -> dict:
         """Returns snapshot."""
         return {
             "certificate_signing_request": self.certificate_signing_request,
             "relation_id": self.relation_id,
+            "is_ca": self.is_ca,
         }
 
     def restore(self, snapshot: dict):
         """Restores snapshot."""
         self.certificate_signing_request = snapshot["certificate_signing_request"]
         self.relation_id = snapshot["relation_id"]
+        self.is_ca = snapshot["is_ca"]
 
 
 class CertificateRevocationRequestEvent(EventBase):
@@ -685,6 +697,7 @@ def generate_certificate(
     ca_key_password: Optional[bytes] = None,
     validity: int = 365,
     alt_names: Optional[List[str]] = None,
+    is_ca: bool = False,
 ) -> bytes:
     """Generates a TLS certificate based on a CSR.
 
@@ -695,6 +708,7 @@ def generate_certificate(
         ca_key_password: CA private key password
         validity (int): Certificate validity (in days)
         alt_names (list): List of alt names to put on cert - prefer putting SANs in CSR
+        is_ca (bool): Whether the certificate is a CA certificate
 
     Returns:
         bytes: Certificate
@@ -726,7 +740,6 @@ def generate_certificate(
         .add_extension(
             x509.SubjectKeyIdentifier.from_public_key(csr_object.public_key()), critical=False
         )
-        .add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=False)
     )
 
     extensions_list = csr_object.extensions
@@ -758,6 +771,29 @@ def generate_certificate(
             critical=extension.critical,
         )
 
+    if is_ca:
+        certificate_builder = certificate_builder.add_extension(
+            x509.BasicConstraints(ca=True, path_length=None), critical=True
+        )
+        certificate_builder = certificate_builder.add_extension(
+            x509.KeyUsage(
+                digital_signature=False,
+                content_commitment=False,
+                key_encipherment=False,
+                data_encipherment=False,
+                key_agreement=False,
+                key_cert_sign=True,
+                crl_sign=True,
+                encipher_only=False,
+                decipher_only=False,
+            ),
+            critical=True,
+        )
+    else:
+        certificate_builder = certificate_builder.add_extension(
+            x509.BasicConstraints(ca=False, path_length=None), critical=False
+        )
+
     certificate_builder._version = x509.Version.v3
     cert = certificate_builder.sign(private_key, hashes.SHA256())  # type: ignore[arg-type]
     return cert.public_bytes(serialization.Encoding.PEM)
@@ -1171,15 +1207,19 @@ def _on_relation_changed(self, event: RelationChangedEvent) -> None:
             certificate_creation_request["certificate_signing_request"]
             for certificate_creation_request in provider_certificates
         ]
-        requirer_unit_csrs = [
-            certificate_creation_request["certificate_signing_request"]
+        requirer_unit_certificate_requests = [
+            {
+                "csr": certificate_creation_request["certificate_signing_request"],
+                "is_ca": certificate_creation_request.get("ca", False),
+            }
             for certificate_creation_request in requirer_csrs
         ]
-        for certificate_signing_request in requirer_unit_csrs:
-            if certificate_signing_request not in provider_csrs:
+        for certificate_request in requirer_unit_certificate_requests:
+            if certificate_request["csr"] not in provider_csrs:
                 self.on.certificate_creation_request.emit(
-                    certificate_signing_request=certificate_signing_request,
+                    certificate_signing_request=certificate_request["csr"],
                     relation_id=event.relation.id,
+                    is_ca=certificate_request["is_ca"],
                 )
         self._revoke_certificates_for_which_no_csr_exists(relation_id=event.relation.id)
 
@@ -1337,8 +1377,17 @@ def __init__(
             self.framework.observe(charm.on.update_status, self._on_update_status)
 
     @property
-    def _requirer_csrs(self) -> List[Dict[str, str]]:
-        """Returns list of requirer's CSRs from relation data."""
+    def _requirer_csrs(self) -> List[Dict[str, Union[bool, str]]]:
+        """Returns list of requirer's CSRs from relation data.
+
+        Example:
+            [
+                {
+                    "certificate_signing_request": "-----BEGIN CERTIFICATE REQUEST-----...",
+                    "ca": false
+                }
+            ]
+        """
         relation = self.model.get_relation(self.relationship_name)
         if not relation:
             raise RuntimeError(f"Relation {self.relationship_name} does not exist")
@@ -1361,11 +1410,12 @@ def _provider_certificates(self) -> List[Dict[str, str]]:
             return []
         return provider_relation_data.get("certificates", [])
 
-    def _add_requirer_csr(self, csr: str) -> None:
+    def _add_requirer_csr(self, csr: str, is_ca: bool) -> None:
         """Adds CSR to relation data.
 
         Args:
             csr (str): Certificate Signing Request
+            is_ca (bool): Whether the certificate is a CA certificate
 
         Returns:
             None
@@ -1376,7 +1426,10 @@ def _add_requirer_csr(self, csr: str) -> None:
                 f"Relation {self.relationship_name} does not exist - "
                 f"The certificate request can't be completed"
             )
-        new_csr_dict = {"certificate_signing_request": csr}
+        new_csr_dict: Dict[str, Union[bool, str]] = {
+            "certificate_signing_request": csr,
+            "ca": is_ca,
+        }
         if new_csr_dict in self._requirer_csrs:
             logger.info("CSR already in relation data - Doing nothing")
             return
@@ -1400,18 +1453,22 @@ def _remove_requirer_csr(self, csr: str) -> None:
                 f"The certificate request can't be completed"
             )
         requirer_csrs = copy.deepcopy(self._requirer_csrs)
-        csr_dict = {"certificate_signing_request": csr}
-        if csr_dict not in requirer_csrs:
-            logger.info("CSR not in relation data - Doing nothing")
+        if not requirer_csrs:
+            logger.info("No CSRs in relation data - Doing nothing")
             return
-        requirer_csrs.remove(csr_dict)
+        for requirer_csr in requirer_csrs:
+            if requirer_csr["certificate_signing_request"] == csr:
+                requirer_csrs.remove(requirer_csr)
         relation.data[self.model.unit]["certificate_signing_requests"] = json.dumps(requirer_csrs)
 
-    def request_certificate_creation(self, certificate_signing_request: bytes) -> None:
+    def request_certificate_creation(
+        self, certificate_signing_request: bytes, is_ca: bool = False
+    ) -> None:
         """Request TLS certificate to provider charm.
 
         Args:
             certificate_signing_request (bytes): Certificate Signing Request
+            is_ca (bool): Whether the certificate is a CA certificate
 
         Returns:
             None
@@ -1422,7 +1479,7 @@ def request_certificate_creation(self, certificate_signing_request: bytes) -> No
                 f"Relation {self.relationship_name} does not exist - "
                 f"The certificate request can't be completed"
             )
-        self._add_requirer_csr(certificate_signing_request.decode().strip())
+        self._add_requirer_csr(certificate_signing_request.decode().strip(), is_ca=is_ca)
         logger.info("Certificate request sent to provider")
 
     def request_certificate_revocation(self, certificate_signing_request: bytes) -> None:
@@ -1701,7 +1758,10 @@ def csr_matches_certificate(csr: str, cert: str) -> bool:
             format=serialization.PublicFormat.SubjectPublicKeyInfo,
         ):
             return False
-        if csr_object.subject != cert_object.subject:
+        if (
+            csr_object.public_key().public_numbers().n  # type: ignore[union-attr]
+            != cert_object.public_key().public_numbers().n  # type: ignore[union-attr]
+        ):
             return False
     except ValueError:
         logger.warning("Could not load certificate or CSR.")