Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 26 additions & 16 deletions test-tools/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# ossl-test-tools

Python tools for OpenSSL's C tests. Edits test artifacts (certs, CRLs,
keys) into the .c source files in place by variable name.
Python tools for OpenSSL's C tests. They generate test artifacts (certs,
CRLs, keys) and edit them into the `.c` source files in place, replacing
the existing `static const ... kName[]` declarations by variable name.

## Setup

Expand All @@ -10,16 +11,31 @@ keys) into the .c source files in place by variable name.

## Use

uv run ossl-test-tools crltest all --source path/to/crltest.c
uv run ossl-test-tools crltest indirect --source path/to/crltest.c
uv run ossl-test-tools crltest alt-ta --source path/to/crltest.c
uv run ossl-test-tools crltest no-chain --source path/to/crltest.c
The first argument names the test to update. It matches the `.c` file it
generates artifacts for, e.g. `crltest` for `test/crltest.c` and
`ocsptest` for `test/ocsptest.c`. After that comes the generator
subcommand. Run `--help` at any level to see what is available and the
parameters each subcommand takes:

Each subcommand has its own `--help`.
uv run ossl-test-tools --help
uv run ossl-test-tools crltest --help
uv run ossl-test-tools ocsptest --help

The generator subcommands take a `--source` pointing at the `.c` file to
rewrite, and `all` regenerates every artifact for that test:

uv run ossl-test-tools crltest all --source path/to/crltest.c
uv run ossl-test-tools crltest indirect --source path/to/crltest.c
uv run ossl-test-tools ocsptest all --source path/to/ocsptest.c

`pem-to-c` formats a PEM file as a C declaration and prints it to stdout,
for one-off pasting:

uv run ossl-test-tools pem-to-c some.pem --name kSomething

## Adding a new variable

Add an empty placeholder to the .c file:
Add an empty placeholder to the `.c` file:

static const char *kSomething[] = {};

Expand All @@ -32,17 +48,11 @@ and register it from `cli.py`.

Two helpers worth knowing:

* `csource` — locate, read, and rewrite `static const ... kName[]`
* `csource` locates, reads, and rewrites `static const ... kName[]`
arrays. PEM string arrays and hex byte arrays are both supported for
reading; only PEM is written.
* `cert_util` cert/CRL/key builders plus `cert_from_c` /
* `cert_util` provides cert/CRL/key builders plus `cert_from_c` /
`update_cert_in_c` bridges.

Per-tool constants (DNs, validity windows, serials) stay in the tool's
own module.

## pem-to-c

Format a PEM file as a C declaration, for one-off pasting:

uv run ossl-test-tools pem-to-c some.pem --name kSomething
3 changes: 2 additions & 1 deletion test-tools/ossl_test_tools/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import argparse

from . import crltest, pem, statem_clnt_construct_test
from . import crltest, ocsptest, pem, statem_clnt_construct_test


def main(argv=None):
Expand All @@ -25,6 +25,7 @@ def main(argv=None):
sub = parser.add_subparsers(dest="cmd", required=True)

crltest.register(sub)
ocsptest.register(sub)
pem.register(sub)
statem_clnt_construct_test.register(sub)

Expand Down
35 changes: 35 additions & 0 deletions test-tools/ossl_test_tools/ocsptest/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html

"""Generators for the artifacts in test/ocsptest.c.

Each subcommand writes its outputs back into the C source file, replacing
the existing kXxx[] declarations by variable name.
"""

from pathlib import Path

from . import pki


def _all_cmd(args):
pki.build(args.source)
print(f"regenerated all OCSP artifacts in {args.source}")


def register(subparsers):
parent = subparsers.add_parser(
"ocsptest",
help="Regenerate artifacts in test/ocsptest.c.",
)
sub = parent.add_subparsers(dest="ocsptest_cmd", required=True)

pki.register(sub)

p = sub.add_parser("all", help="Run all OCSP artifact generators.")
p.add_argument("--source", type=Path, required=True, help="Path to ocsptest.c")
p.set_defaults(func=_all_cmd)
117 changes: 117 additions & 0 deletions test-tools/ossl_test_tools/ocsptest/pki.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html

"""PKI for test/ocsptest.c.

kOcspTestRoot self-signed root CA, trust anchor and OCSP responder
kOcspTestRootKey the root CA private key, used to sign OCSP responses
kOcspTestLeaf leaf issued by the root CA

The chain is flat (no intermediate) so the root is the leaf's issuer and
therefore its authorized OCSP responder. Validity windows are long because
the verification path checks OCSP response validity against the wall clock.
"""

import datetime
from pathlib import Path

from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.x509.oid import ExtendedKeyUsageOID

from .. import cert_util

UTC = datetime.timezone.utc
NOT_BEFORE = datetime.datetime(2026, 1, 1, 0, 0, 0, tzinfo=UTC)
NOT_AFTER = datetime.datetime(2126, 1, 1, 0, 0, 0, tzinfo=UTC)

ROOT_DN = cert_util.name(
"US", "California", "San Francisco",
"Example Corp", "Certificate Authority",
"Example Corp OCSP Test Root CA",
)
LEAF_DN = cert_util.name(
"US", "California", "San Francisco",
"Example Corp", "Web Services",
"ocsp-leaf.example.com",
)


def _ca_key_usage():
return x509.KeyUsage(
digital_signature=True, 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)


def _leaf_key_usage():
return x509.KeyUsage(
digital_signature=True, content_commitment=False,
key_encipherment=True, data_encipherment=False, key_agreement=False,
key_cert_sign=False, crl_sign=False,
encipher_only=False, decipher_only=False)


def build(source_path):
root_key = cert_util.new_rsa_key()
root = (
x509.CertificateBuilder()
.subject_name(ROOT_DN)
.issuer_name(ROOT_DN)
.public_key(root_key.public_key())
.serial_number(0x1)
.not_valid_before(NOT_BEFORE)
.not_valid_after(NOT_AFTER)
.add_extension(x509.BasicConstraints(ca=True, path_length=0), critical=True)
.add_extension(_ca_key_usage(), critical=True)
.add_extension(
x509.SubjectKeyIdentifier.from_public_key(root_key.public_key()),
critical=False)
.sign(private_key=root_key, algorithm=hashes.SHA256())
)

leaf_key = cert_util.new_rsa_key()
leaf = (
x509.CertificateBuilder()
.subject_name(LEAF_DN)
.issuer_name(ROOT_DN)
.public_key(leaf_key.public_key())
.serial_number(0x1000)
.not_valid_before(NOT_BEFORE)
.not_valid_after(NOT_AFTER)
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
.add_extension(_leaf_key_usage(), critical=True)
.add_extension(
x509.ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH]), critical=False)
.add_extension(
x509.SubjectKeyIdentifier.from_public_key(leaf_key.public_key()),
critical=False)
.add_extension(cert_util.akid_from_cert(root), critical=False)
.add_extension(
x509.SubjectAlternativeName([x509.DNSName("ocsp-leaf.example.com")]),
critical=False)
.sign(private_key=root_key, algorithm=hashes.SHA256())
)

cert_util.update_cert_in_c(source_path, "kOcspTestRoot", root)
cert_util.update_key_in_c(source_path, "kOcspTestRootKey", root_key)
cert_util.update_cert_in_c(source_path, "kOcspTestLeaf", leaf)
Comment thread
bukka marked this conversation as resolved.


def _cmd(args):
build(args.source)
print(f"updated kOcspTestRoot kOcspTestRootKey kOcspTestLeaf in {args.source}")


def register(sub):
p = sub.add_parser(
"pki",
help="Regenerate the root CA, root key, and leaf.",
)
p.add_argument("--source", type=Path, required=True, help="Path to ocsptest.c")
p.set_defaults(func=_cmd)