A certificate-issuance integration can look reassuring long before its output deserves that confidence. The issuer configuration contains a leaf profile. The CSR has a correct self-signature. openssl ca exits successfully. Yet the operational question remains: which extension values actually reached the signed certificate?
This playbook answers that question for three selected fields (basicConstraints, keyUsage, and DNS subjectAltName) under OpenSSL ca with copy_extensions=none, copy, and copyall. OpenSSL documents those modes as command-specific behavior: none ignores request extensions, copy adds requested extensions that are not already present, and copyall copies requested extensions after deleting certificate extensions of the same type. The same manual explicitly warns about unintentionally granting CA privileges when request extensions are copied without adequate issuer controls.
The evidence here is the supplied commissioning experiment prepared for this article: two authored CSRs, eight isolated issuance cases, and structured readback with cryptography 50.0.2. Three outputs matched the deliberately narrow leaf-field contract; five correctly produced policy holds. A match means only MATCHES_TESTED_EXTENSION_FIELDS. It does not authorize requester identity, DNS entitlement, certificate publication, trust, or production use.
The issuing executable in that record was OpenSSL CLI 3.0.13. The separately reported Python OpenSSL backend was 4.0.3. Those are different runtime identities and must not be merged.
Define the issuance decision before examining a CSR
The artifact under review is the newly signed X.509 certificate, not the issuer configuration by itself and not the request by itself. The integration must compare selected emitted fields against a profile declared independently of requester-controlled input.
This playbook uses three outcomes:
Decision | Meaning |
MATCHES_TESTED_EXTENSION_FIELDS | The signed certificate exactly matches the selected three-field oracle. |
HOLD_PROFILE_MISMATCH | At least one selected emitted field or critical flag differs from that oracle. |
HOLD_INCOMPLETE_EVIDENCE | Required execution, case inventory, parsed output, provenance, or oracle evidence is missing or unexpected. |
A zero process exit is therefore one observation, not the issuance decision. A valid CSR self-signature is another observation. Structured inspection of the emitted certificate is a third. Business approval remains separate.
That boundary matters because downstream testing answers different questions. Refonte Learning's article on TLS probes that enforce hostname verification concerns how a client evaluates a server certificate during a TLS probe; this experiment stops earlier, at certificate construction. The OpenSSL ca manual likewise describes request-extension handling for this command rather than a complete certificate-admission or trust system.
The core review rule is simple: declare the issuer-owned fields first, issue, parse the result independently, and compare exact values. Do not derive the expected profile by looking at whatever OpenSSL happened to emit.
Declare the three extension fields the issuer owns
The illustrative leaf contract is deliberately small:
basicConstraints: critical, CA:FALSE, path_length=None
keyUsage: critical, digitalSignature,keyEncipherment
subjectAltName: noncritical, DNS:api.lab.testOpenSSL's X.509 configuration documentation supplies the syntax used by the fixture: critical marks an extension critical; basicConstraints accepts CA:TRUE or CA:FALSE and an optional pathlen; keyUsage accepts named usage bits; and subjectAltName accepts DNS names among several supported GeneralName forms. The documentation describes configuration vocabulary, not requester entitlement.
This exact contract is an authored test expectation, not a universal recommendation for TLS certificates. Extended Key Usage is intentionally absent from the oracle. Certificate policies, name constraints, SKI/AKI behavior, validity rules, algorithms, subjects, unknown OIDs, and all other profile elements remain outside this experiment.
That separation also distinguishes this review from broader certificate lifecycle automation, which deals with issuance, deployment, renewal, and operational ownership across a certificate lifecycle. Here, the decision is narrower: what did one issuance command construct in three selected extension types?
Make the leaf-field contract independently reviewable
The issuer profile should be reviewable without opening a generated certificate. A profile owner decides that the experiment expects CA:FALSE, two exact key-usage bits, and exactly one DNS SAN. A separate evidence reviewer can then inspect the signed output without redefining success after seeing it.
That avoids a common testing failure: reading the output, deciding that its values “look normal,” and then treating those observed values as expected values. A reproducible security test needs an oracle defined before execution.
For this fixture, exact equality includes the critical flags and path_length. A basicConstraints extension with the correct CA boolean but a different criticality or path-length value would not satisfy the selected-field contract.
Inspect two valid requests with unacceptable demands
The fixture authors two CSRs. Both request:
basicConstraints = critical,CA:TRUE
keyUsage = critical,keyCertSign,cRLSignThe base CSR requests:
subjectAltName = DNS:api.lab.testThe extra-name CSR requests:
subjectAltName = DNS:api.lab.test,DNS:admin.lab.testThose values deliberately conflict with the leaf oracle. The request asks for CA role and CA-oriented signing usages, while the extra-name request also asks for a second DNS identity.
openssl req -addext is the construction mechanism for these request extensions. OpenSSL documents -addext as adding an extension in configuration-style key=value form. It also gives req -x509 its own -copy_extensions semantics; those semantics are not interchangeable evidence for openssl ca. OpenSSL 3.0 openssl-req and the separately documented openssl x509 request-copy behavior reinforce that these commands have distinct option boundaries.
The supplied run parsed each CSR with cryptography, verified its self-signature, and checked that its authored extension fields exactly matched the intended hostile input before issuance. That matters: a negative test is weak if the supposedly conflicting input was never confirmed.
Treat self-signature validity as an input precondition
cryptography 50.0.2 documents CertificateSigningRequest.is_signature_valid as returning whether the CSR signature is correct. Its PEM loaders deserialize CSR and certificate structures, and get_extension_for_class() provides typed extension lookup. Those are appropriate tools for structural inspection; deserialization itself is not trust validation.
For these two inputs, a correct CSR self-signature establishes a narrow fact: the signature is consistent with the public key carried by that request. It does not establish who submitted the request, whether that party controls either DNS name, whether the party may obtain a CA certificate, or whether any requested extension should be honored.
The request is evidence to evaluate. It is not the default issuer policy.
Run the isolated eight-case issuance fixture
The complete fixture below is the supplied security_csr_fixture.py. It creates an owned temporary root, one owned request key, two CSRs, eight separate CA databases and eight signed outputs. It uses no hidden fixture files or external certificate service.
Provision the dependencies before the offline experiment:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install "cryptography==50.0.2"
openssl version -a
python security_csr_fixture.py > security_csr_results.jsonInstallation is preparation. The fixture itself calls local processes and uses temporary owned files. It does not modify the system trust store or contact a certificate service.
#!/usr/bin/env python3
"""Owned, offline OpenSSL ca extension-precedence experiment; not a CA service."""
import datetime
import hashlib
import json
import os
from pathlib import Path
import platform
import shutil
import subprocess
import tempfile
import cryptography
from cryptography import x509
from cryptography.hazmat.backends.openssl.backend import backend
def require(condition, label):
if not condition:
raise RuntimeError(label)
def fields(obj):
bc = obj.extensions.get_extension_for_class(x509.BasicConstraints)
ku = obj.extensions.get_extension_for_class(x509.KeyUsage)
bits = [name for name in (
"digital_signature", "content_commitment", "key_encipherment",
"data_encipherment", "key_agreement", "key_cert_sign", "crl_sign"
) if getattr(ku.value, name)]
if ku.value.key_agreement:
bits.extend(name for name in ("encipher_only", "decipher_only")
if getattr(ku.value, name))
try:
san = obj.extensions.get_extension_for_class(x509.SubjectAlternativeName)
require(all(isinstance(n, x509.DNSName) for n in san.value),
"fixture scope contains only DNS SAN entries")
names, san_critical = [n.value for n in san.value], san.critical
except x509.ExtensionNotFound:
names, san_critical = None, None
return {"ca": bc.value.ca, "path_length": bc.value.path_length,
"bc_critical": bc.critical, "key_usage": bits,
"ku_critical": ku.critical, "dns_sans": names,
"san_critical": san_critical}
LEAF_BITS = ["digital_signature", "key_encipherment"]
CA_BITS = ["key_cert_sign", "crl_sign"]
API = ["api.lab.test"]
EXTRA = ["api.lab.test", "admin.lab.test"]
def expected(ca, bits, names):
return {"ca": ca, "path_length": None, "bc_critical": True,
"key_usage": bits, "ku_critical": True, "dns_sans": names,
"san_critical": None if names is None else False}
def mismatches(observed):
problems = []
if (observed["ca"], observed["path_length"], observed["bc_critical"]) != (
False, None, True):
problems.append("basicConstraints")
if (observed["key_usage"], observed["ku_critical"]) != (LEAF_BITS, True):
problems.append("keyUsage")
if (observed["dns_sans"], observed["san_critical"]) != (API, False):
problems.append("subjectAltName")
return problems
# Explicit expectations precede execution and are not inferred from signed output.
CASES = [
("E01", "none", True, False, "base", False, LEAF_BITS, None,
["subjectAltName"]),
("E02", "copyall", True, False, "base", True, CA_BITS, API,
["basicConstraints", "keyUsage"]),
("E03", "copy", True, False, "base", False, LEAF_BITS, API, []),
("E04", "none", True, True, "base", False, LEAF_BITS, API, []),
("E05", "copy", False, False, "base", False, CA_BITS, API, ["keyUsage"]),
("E06", "copy", True, False, "extra", False, LEAF_BITS, EXTRA,
["subjectAltName"]),
("E07", "copy", True, True, "extra", False, LEAF_BITS, API, []),
("E08", "copyall", True, True, "extra", True, CA_BITS, EXTRA,
["basicConstraints", "keyUsage", "subjectAltName"]),
]
def main():
started = datetime.datetime.now(datetime.timezone.utc).isoformat()
binary = shutil.which("openssl")
require(binary is not None, "openssl executable required")
binary = str(Path(binary).resolve())
commands, rows, requests = [], [], {}
with tempfile.TemporaryDirectory(prefix="owned-csr-policy-") as temp:
work = Path(temp)
config = work / "req.cnf"
config.write_text("[req]\nprompt=no\ndistinguished_name=dn\n"
"[dn]\nCN=Owned Copy Policy Root\n")
env = {"PATH": os.environ.get("PATH", ""), "LC_ALL": "C",
"OPENSSL_CONF": str(config)}
def run(*args):
command = [binary, map(str, args)]
p = subprocess.run(command, capture_output=True, text=True,
env=env, timeout=30, check=False)
commands.append({"argv": command, "returncode": p.returncode,
"stdout": p.stdout, "stderr": p.stderr})
require(p.returncode == 0, "OpenSSL command failed: " + str(args[0]))
return p.stdout.strip()
cli_version = run("version", "-a")
root_key, root_cert = work / "root.key", work / "root.pem"
run("req", "-new", "-x509", "-newkey", "rsa:2048", "-noenc",
"-keyout", root_key, "-out", root_cert, "-days", "2", "-sha256",
"-config", config, "-addext", "basicConstraints=critical,CA:TRUE",
"-addext", "keyUsage=critical,keyCertSign,cRLSign")
key = work / "request.key"
for kind, names in (("base", API), ("extra", EXTRA)):
csr = work / (kind + ".csr")
key_args = ["-newkey", "rsa:2048", "-noenc", "-keyout", key]
if kind == "extra":
key_args = ["-key", key]
run("req", "-new", key_args, "-config", config,
"-subj", "/CN=api.lab.test", "-out", csr, "-sha256",
"-addext", "basicConstraints=critical,CA:TRUE",
"-addext", "keyUsage=critical,keyCertSign,cRLSign",
"-addext", "subjectAltName=" + ",".join("DNS:" + n for n in names))
parsed = x509.load_pem_x509_csr(csr.read_bytes())
require(parsed.is_signature_valid, "CSR self-signature")
require(fields(parsed) == expected(True, CA_BITS, names), "CSR fields")
requests[kind] = {"self_signature_valid": True, "fields": fields(parsed)}
for ident, mode, set_ku, set_san, kind, ca, bits, names, failures in CASES:
case = work / ident
case.mkdir()
(case / "newcerts").mkdir()
(case / "index.txt").write_text("")
(case / "serial").write_text(f"{0x1000 + int(ident[1:]):X}\n")
profile = "basicConstraints=critical,CA:FALSE\n"
if set_ku:
profile += "keyUsage=critical,digitalSignature,keyEncipherment\n"
if set_san:
profile += "subjectAltName=DNS:api.lab.test\n"
ca_config = case / "ca.cnf"
ca_config.write_text(
"[ca]\ndefault_ca=lab\n[lab]\n"
f"database={case}/index.txt\nnew_certs_dir={case}/newcerts\n"
f"serial={case}/serial\ncertificate={root_cert}\nprivate_key={root_key}\n"
"default_days=1\ndefault_md=sha256\npolicy=names\n"
f"x509_extensions=leaf\ncopy_extensions={mode}\n"
"[names]\ncommonName=supplied\n[leaf]\n" + profile)
cert = case / "issued.pem"
run("ca", "-batch", "-config", ca_config, "-in", work / (kind + ".csr"),
"-notext", "-out", cert)
actual = fields(x509.load_pem_x509_certificate(cert.read_bytes()))
wanted = expected(ca, bits, names)
actual_failures = mismatches(actual)
require(actual == wanted, ident + " emitted fields differ")
require(actual_failures == failures, ident + " mismatch oracle differs")
rows.append({"case": ident, "mode": mode, "issuer_sets_ku": set_ku,
"issuer_sets_san": set_san, "request": kind,
"expected": wanted, "observed": actual,
"expected_mismatches": failures, "mismatches": actual_failures,
"decision": "HOLD_PROFILE_MISMATCH" if actual_failures else
"MATCHES_TESTED_EXTENSION_FIELDS", "status": "PASS"})
require(not work.exists(), "temporary directory cleanup")
report = {"status": "PASS_8_CASES", "started_utc": started,
"completed_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(),
"python": platform.python_version(), "platform": platform.platform(),
"cryptography": cryptography.__version__,
"cryptography_openssl_backend": backend.openssl_version_text(),
"openssl_binary": binary, "openssl_version_a": cli_version,
"openssl_binary_sha256": hashlib.sha256(Path(binary).read_bytes()).hexdigest(),
"source_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
"temporary_directory_removed": True, "requests": requests,
"cases": rows, "commands": commands}
print(json.dumps(report, indent=2))
if name == "__main__":
main()Several implementation details are deliberate. CASES contains expected emitted values before any certificate is parsed. fields() uses typed X.509 structures rather than matching openssl display text. mismatches() compares the result against the independent leaf-field contract, not against each case's predicted OpenSSL behavior. The test therefore distinguishes “OpenSSL behaved as predicted” from “the output satisfies issuer policy.”
OpenSSL's documented behavior explains the matrix: none ignores request extensions; copy copies only request extension types that the certificate does not already contain; copyall copies all request extensions and removes existing certificate extensions of the same type first. The manual specifically recommends caution because a request containing basicConstraints=CA:TRUE can become dangerous when copied inappropriately, and it advises explicitly setting other issuer-controlled fields such as keyUsage.
The supplied commissioning record reported:
status: PASS_8_CASES
started: 2026-11-04T08:27:15.619418+00:00
completed: 2026-11-04T08:27:18.885736+00:00
source SHA-256:
83d35c26a717abebe30a50a29550106267a4842cb9b24dc622b304596da15d7fIts environment was Linux x86_64 with glibc 2.39, Python 3.12.14, and cryptography 50.0.2. The issuing executable was /usr/bin/openssl; its CLI and linked library identified themselves as OpenSSL 3.0.13 30 Jan 2024, and its binary SHA-256 was 204951390b4f82d39f98b0dd0fa54502f27256c8d2fd08b55a5e3781408ae83b. The Python package separately reported an OpenSSL backend of 4.0.3 29 Sep 2026. This is therefore an OpenSSL 3.0.13 issuing-CLI experiment, not an OpenSSL 4.0.3 issuance experiment.
All eight ca invocations returned zero. Both CSR self-signatures passed their precondition. E03, E04, and E07 matched the selected contract; E01, E02, E05, E06, and E08 were expected policy holds.
A reviewer should verify more than the top-level PASS_8_CASES: confirm all eight case identities are present, inspect each expected and observed object, verify the mismatch arrays and decisions, and retain the recorded command results. If execution stops and leaves truncated JSON, if a row is missing, or if an oracle assertion fails, the result is HOLD_INCOMPLETE_EVIDENCE.
The fixture also checks normal temporary-directory removal. That is not secure erasure. It never prints private-key bytes.
Finally, OpenSSL warns that openssl ca is not a production-quality CA service and that its database operations have no locking for concurrent use. The fixture deliberately uses independent databases sequentially; it qualifies neither concurrent issuance nor production key custody.
Read what copyall replaces in the signed certificate
E02 and E08 demonstrate the strongest precedence behavior in this experiment.
In E02, the issuer configured:
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEnciphermentThe base request supplied CA:TRUE, keyCertSign,cRLSign, and DNS:api.lab.test. With copy_extensions=copyall, the emitted certificate contained CA:TRUE, CA-oriented key usage, and the requested API SAN. The configured basicConstraints and keyUsage values were replaced.
E08 makes the SAN conflict explicit. The issuer configured all three leaf fields, including only DNS:api.lab.test. The request contained api.lab.test plus admin.lab.test. With copyall, the signed output contained the request's CA:TRUE, its CA key usages, and its two-name SAN.
That is exactly consistent with the OpenSSL ca rule: if a request extension type already exists in the certificate under construction, copyall removes that existing extension before copying the requested one.
Reconcile configured and emitted role and usage
The distinction is extension-level, not a merge of “safe” individual values.
Field | Issuer intended | CSR requested | E08 emitted |
basicConstraints | CA:FALSE | CA:TRUE | CA:TRUE |
keyUsage | digital signature, key encipherment | keyCertSign, cRLSign | keyCertSign, cRLSign |
DNS SAN | api.lab.test | api.lab.test, admin.lab.test | both names |
The correct negative-control result is therefore HOLD_PROFILE_MISMATCH, not an assumption that OpenSSL should have rejected issuance. The command's documented job includes copying request extensions under this mode. Application policy must decide whether the resulting certificate is acceptable.
Nor does emitted CA:TRUE demonstrate that the certificate has become a trusted authority. This experiment does not add the root to any trust store, construct or validate a certification path, or exercise downstream CA operations.
Compare none with an explicit issuer SAN
E01 and E04 use the same base request and copy_extensions=none. The difference is whether the issuer configuration supplies SAN itself.
E01 configures basicConstraints and keyUsage, but not subjectAltName. Because none ignores requested extensions, the requested SAN does not appear in the issued certificate. The configured role and usage remain correct, while the SAN is absent. The parser records absence as:
"dns_sans": null,
"san_critical": nullThat is a missing required field, not an incorrect SAN value.
E04 adds the issuer-owned SAN:
subjectAltName=DNS:api.lab.testNow the same none behavior suppresses the request extensions while the issuer profile itself supplies every field in the oracle. E04 becomes MATCHES_TESTED_EXTENSION_FIELDS.
The practical lesson is not that none is universally preferable. It is that suppressing request copying does not synthesize omitted required values. If the issuer owns a SAN value, the issuer must obtain that value from an approved source and configure or construct it explicitly.
RFC 5280 (Section 4.2.1.6) treats Subject Alternative Name as identities bound by the issuer to the subject's public key and requires the CA to verify those identities according to its procedures. That normative responsibility is separate from OpenSSL's copying mechanism.
Protect existing fields with copy and test the omissions
E03 shows the attractive part of copy_extensions=copy: issuer-configured extension types win, while absent types may be filled from the CSR.
The issuer supplies basicConstraints=CA:FALSE and the leaf keyUsage, but no SAN. The base CSR requests conflicting CA role and usage plus DNS:api.lab.test. Because the certificate already has the first two extension types, copy does not replace them; because SAN is absent, the request SAN is copied. E03 matches all three tested fields.
That result can easily be overgeneralized. E05 exists to prevent that mistake.
E05 still configures CA:FALSE, but the issuer omits both keyUsage and SAN. Under copy, both missing extension types are admitted from the CSR. The output therefore contains:
basicConstraints: CA:FALSE
keyUsage: keyCertSign,cRLSign
SAN: DNS:api.lab.testThe fixture holds E05 for a keyUsage mismatch.
RFC 5280 (Sections 4.2.1.3 and 4.2.1.9) states that when the keyCertSign bit is asserted, the cA bit in basicConstraints must also be asserted. The E05 combination therefore illustrates why independently controlling related fields matters; this article does not present that output as a useful or valid CA certificate.
An omitted keyUsage is still copied
copy is a precedence rule, not a comprehensive allowlist. It protects an extension type only when the certificate under construction already contains that type.
That makes omission a security-relevant test input. A benign CSR containing the same values as the issuer profile would not reveal the boundary. E05 asks the stronger question: if a supposedly issuer-owned extension disappears from configuration, can the request begin determining it?
In the supplied run, the answer for keyUsage was yes. The corrective action is to restore deliberate ownership and rerun the full suite, not merely to note that basicConstraints remained CA:FALSE.
Keep extra requested DNS names outside the approved output
E06 and E07 isolate SAN ownership under copy.
E06 configures leaf basicConstraints and keyUsage, but omits SAN. The extra-name CSR asks for:
DNS:api.lab.test,DNS:admin.lab.testBecause subjectAltName is absent from the issuer's extension section, copy admits that whole requested extension. The parser returns the exact ordered list:
["api.lab.test", "admin.lab.test"]The oracle expects only ["api.lab.test"], so E06 is HOLD_PROFILE_MISMATCH.
E07 differs only by explicitly configuring:
subjectAltName=DNS:api.lab.testNow the issuer already has a SAN extension. Under the measured copy behavior, the request's SAN does not replace it, and the output contains only api.lab.test. E07 matches the selected fields.
The fixture deliberately accepts only x509.DNSName objects inside SAN. Encountering another GeneralName form stops execution rather than silently ignoring it. OpenSSL supports several SAN forms beyond DNS, including email, URI, IP, registered IDs, directory names, and otherName; those forms are outside this experiment.
Separate exact SAN equality from name authorization
Exact equality is valuable because an unnoticed additional DNS name is materially different from the intended one-name contract. But exact output equality does not answer whether api.lab.test should have been issued to this requester.
E07 therefore means only that issuer-owned construction produced the tested SAN value. A separate request-admission system still needs to decide which identities a requester may obtain. RFC 5280 places responsibility on the issuer to verify identities carried in SAN; this fixture does not implement that verification process.
For the same reason, fixed-SAN substitution should not be presented as a universal certificate adapter. A real integration must derive approved names through its own authorization process.
Reconcile all eight outputs with the independent oracle
The complete measured picture is compact:
Case | Mode | Issuer KU / SAN present | Emitted CA / KU / SAN | Field mismatch |
E01 | none | yes / no | false / leaf / absent | SAN |
E02 | copyall | yes / no | true / CA / api | BC, KU |
E03 | copy | yes / no | false / leaf / api | none |
E04 | none | yes / yes | false / leaf / api | none |
E05 | copy | no / no | false / CA / api | KU |
E06 | copy | yes / no | false / leaf / extra | SAN |
E07 | copy | yes / yes | false / leaf / api | none |
E08 | copyall | yes / yes | true / CA / extra | BC, KU, SAN |
Here, leaf KU means exactly digital_signature plus key_encipherment; CA KU means exactly key_cert_sign plus crl_sign; api is exactly ["api.lab.test"]; and extra is exactly ["api.lab.test", "admin.lab.test"].
All observed basicConstraints and keyUsage instances were critical. SAN was noncritical when present. Every observed path_length was null.
The result PASS_8_CASES means all eight planned observations agreed with the independent per-case oracle, including five outputs that deliberately failed the leaf-profile check. It does not mean eight acceptable certificates.
Inventory completeness is part of the evidence. A report containing only E03, E04, and E07 is not a passing subset; it is incomplete evidence. Likewise, a count of eight rows without expected case identities is insufficient because duplicated or substituted rows could conceal a missing negative control.
The matrix is also version-bound evidence. The documented OpenSSL 3.0 semantics align with every observation here, so there is no documented conflict to reconcile for the tested ca modes. Other runtimes should produce and retain their own qualification record rather than inheriting this one.
Decide what a matched profile establishes
The operational decision table should remain deliberately conservative:
Evidence | Decision | Next action | Primary owner |
All three selected fields, flags, and path length match | MATCHES_TESTED_EXTENSION_FIELDS | Continue to independent requester, profile, trust and publication controls | Issuance/release reviewer |
Any selected field differs | HOLD_PROFILE_MISMATCH | Stop promotion; identify profile omission or precedence issue; correct and rerun | Issuer-profile owner |
Missing case, malformed/truncated report, failed command, unexpected inventory, failed oracle | HOLD_INCOMPLETE_EVIDENCE | Re-establish provenance and rerun complete test | Test/evidence owner |
Do not rename the first outcome ACCEPT_CERTIFICATE. The parser intentionally checks only three extension types. Unknown OIDs, EKU, policy extensions, subject constraints, validity, algorithms, serial practices, issuer properties, and other profile requirements remain unqualified.
cryptography's typed X.509 objects make the selected readback reproducible, but its loading functions are deserializers, not blanket trust decisions.
Reserve trust and publication for separate approval
Certificate construction, request authorization, trust validation, and downstream admission are different control points. Refonte Learning's separate treatment of CMS signer selection and admission makes the same general engineering distinction at another boundary: cryptographic success and application authorization are not synonyms.
This fixture never installs its temporary root, never validates a chain for an external consumer, never publishes a certificate, and never tests revocation. CA:TRUE in E02 or E08 is therefore only an emitted X.509 field observed in the signed output.
A MATCHES_TESTED_EXTENSION_FIELDS decision is evidence that three selected construction properties matched. Nothing more should be inferred.
Retain enough evidence to explain each decision
A useful regression record should retain enough context to reconstruct why a case passed or was held:
case identity and request variant;
copy_extensions mode;
which issuer-controlled fields were present or intentionally omitted;
independently declared expected values;
structurally parsed emitted values, critical flags, and path_length;
mismatch reasons and resulting decision;
source SHA-256;
actual OpenSSL executable path, binary hash, CLI/library version output, Python version, cryptography version, and Python OpenSSL backend identity;
command return codes and relevant diagnostics;
configuration provenance supplied by the real integration.
That record resembles the broader principle behind security decision logs and observability: retain the inputs and decision context needed to explain why a security control reached its result. The laboratory JSON is one such local evidence artifact, not an independently trusted attestation.
The source hash 83d35c26a717abebe30a50a29550106267a4842cb9b24dc622b304596da15d7f identifies the supplied fixture bytes. It does not prove that an arbitrary JSON report was actually produced from those bytes, prove who executed them, or authorize any certificate.
Do not log private keys. Likewise, do not treat this owned laboratory's harmless CSR contents as permission to copy sensitive production requests into CI logs.
Random keys, temporary paths, validity timestamps, generated certificate bytes, and diagnostic details may vary on another run. Preserve semantic expectations and capture the actual runtime rather than demanding byte-identical certificates.
Correct the profile and recover from a held output
A profile hold should interrupt promotion of that laboratory output, not trigger ad hoc reinterpretation of the oracle.
A bounded recovery sequence is:
Preserve the failing evidence: case ID, configuration, parsed output, mismatch reason, command result, and runtime provenance.
Identify the ownership defect. Examples from this fixture include an omitted SAN under none, an omitted keyUsage under copy, or inappropriate use of copyall when issuer-owned extensions must not be replaced.
Have the responsible profile owner revise the intended issuer configuration or request-admission design through normal review.
Rerun the complete eight-case comparison, including the negative controls.
Accept the regression evidence only if every planned case is present and the independent oracle passes.
Changing configuration does not alter an already signed certificate. The old certificate remains the artifact that was produced under the previous configuration.
If an analogous production certificate had already been distributed, containment, replacement, publication control, and revocation would belong to that organization's separate certificate-management procedures. Nothing in this temporary laboratory performs those operations, and normal deletion of its temporary directory is not revocation or secure erasure.
The essential engineering rule is to recover by changing the responsible policy mechanism and reproducing evidence, rather than by weakening the expected values until an undesirable output appears acceptable.
Assign policy ownership and regression triggers
This test is most useful when each decision has a named engineering owner.
The issuer-profile owner owns fields the issuer intends to control and reviews changes to extension sections and copy behavior. The request/name-approval owner decides which requester-controlled identities or privileges may enter issuance. The test owner maintains positive and adversarial cases. The release or issuance reviewer checks that evidence is complete before an integration is relied upon.
This fits the broader discipline of automated security testing in CI/CD: repeatable controls are most useful when they run against known expectations and preserve actionable failure evidence. The present fixture is specifically a certificate-construction regression test, not a substitute for SAST, DAST, dependency testing, or a broader release-security program.
Requalification should be triggered when the issuing executable or relevant package changes, when x509_extensions or related issuer configuration changes, when copy rules change, when new request sources are admitted, or when the integration begins supporting extension types this oracle does not parse.
Keep both matching and mismatching controls. A suite that signs only a benign CSR cannot demonstrate precedence when the request conflicts with policy. E02 and E08 establish replacement behavior; E05 tests omission of keyUsage; E06 tests admission of an extra DNS name; E01 tests a required issuer field that request suppression does not magically supply.
Acceptance criteria should include the expected source identity, explicit runtime versions, both valid CSR preconditions, eight unique cases, successful structured parsing, exact expected-versus-observed equality, expected mismatch labels, and normal cleanup reporting. Any missing element becomes HOLD_INCOMPLETE_EVIDENCE.
Do not extend that acceptance to production-CA properties the fixture never exercises. OpenSSL's own ca documentation warns that its sample-style database is not designed for concurrent use. This sequential test says nothing about production database locking, HSMs, multi-process issuance, high availability, revocation infrastructure, or key-custody architecture.
Build the security foundations behind issuance review
The operational answer is now specific. Under OpenSSL ca 3.0 semantics, none ignores CSR extensions; copy preserves extension types already supplied by the issuer while copying requested types that are absent; and copyall allows requested extension types to replace configured ones. The supplied eight-case run on OpenSSL CLI 3.0.13 reproduced those rules for basicConstraints, keyUsage, and DNS SAN.
For an integration review, the evidence that allows work to proceed is therefore not “the signing command succeeded.” It is a complete, provenance-bearing comparison showing which fields the issuer intended, which values the CSR requested, which values were emitted, and whether the selected output matches the independent contract. Even then, MATCHES_TESTED_EXTENSION_FIELDS remains only a construction-time result.
Engineers building the broader skills behind that review can inspect the Refonte Learning - Cybersecurity Program. Its current page lists Cryptography and Secure Data Management, Secure CI/CD Pipelines, DAST/SAST/IAST, OWASP and Threat Modeling Practices, Incident Response and Risk Mitigation, and Security Workflow Automation among its competencies. It lists a workload of 12–14 hours per week. The page does not establish that this particular OpenSSL ca fixture or CSR-copying workflow is taught, so readers interested in certificate-policy testing should review the curriculum and ask Refonte Learning whether the program fits that learning need.
