Update: replace hash symlink with CA bundle for certificate verification
All checks were successful
/ test (push) Successful in 17s

This commit is contained in:
2026-04-24 23:25:44 +02:00
parent c29537c9e6
commit c043410436
4 changed files with 39 additions and 48 deletions

View File

@@ -23,7 +23,7 @@ make_ca [--days <validity_days>] [--issuing-ca <name>] <ca_directory> <ca_name>
- `--days <validity_days>`: Optional. The number of days the CA certificate will be valid. Default is 3650 days (10 years).
- `--issuing-ca <name>`: Optional. If specified, creates an intermediate CA with <ca_name> as the intermediate CA name and using <name> as certificate and key file prefix for the issuing CA (instead of root's `ca`).
It also creates a *hash link* symbolic link for the CA certificate, which is required by OpenSSL when using the `verify` command with `-CApath` option.
It also maintains a `ca_bundle.pem` file in the CA directory containing the root CA and any issuing CA certificates concatenated together. Use this bundle with `openssl verify -CAfile <ca_directory>/ca_bundle.pem` instead of relying on hash symlinks — this works identically on Linux, macOS, and Windows without symlink privileges.
### `make_cert()`

View File

@@ -46,10 +46,10 @@ function display_certificate() {
openssl x509 -in "$CERT_PATH" -noout -subject -issuer -serial -fingerprint
echo
echo -e "\nVerifying certificate against the CA ($CA_DIR)..."
echo -e "\nVerifying certificate against the CA bundle ($CA_DIR/ca_bundle.pem)..."
# Verify the certificate against the CA
if openssl verify -CApath "$CA_DIR" "$CERT_PATH" 2>/dev/null; then
# Verify the certificate against the CA bundle
if openssl verify -CAfile "$CA_DIR/ca_bundle.pem" "$CERT_PATH" 2>/dev/null; then
echo "Certificate verification successful."
else
echo "ERROR: Certificate verification failed." >&2

View File

@@ -33,32 +33,23 @@ def _err(msg):
print(f"ERROR: {msg}", file=sys.stderr)
def make_hash_link(cert_path):
if not os.path.isfile(cert_path):
_err(f"Certificate file {cert_path} does not exist.")
return False
cert_dir = os.path.dirname(cert_path)
try:
result = subprocess.run(
["openssl", "x509", "-in", cert_path, "-noout", "-hash"],
capture_output=True, text=True, check=True,
)
except subprocess.CalledProcessError:
_err(f"Failed to calculate hash for certificate {cert_path}.")
return False
cert_hash = result.stdout.strip()
if not cert_hash:
_err(f"Failed to calculate hash for certificate {cert_path}.")
return False
link_path = os.path.join(cert_dir, f"{cert_hash}.0")
target = os.path.basename(cert_path)
if os.path.islink(link_path) or os.path.exists(link_path):
os.remove(link_path)
os.symlink(target, link_path)
return True
def _rebuild_ca_bundle(ca_dir):
"""Write ca_bundle.pem = root cert + any issuing CA certs in this dir."""
bundle_path = os.path.join(ca_dir, "ca_bundle.pem")
parts = []
root = os.path.join(ca_dir, "ca_cert.pem")
if os.path.isfile(root):
with open(root, "rb") as f:
parts.append(f.read())
for name in sorted(os.listdir(ca_dir)):
if name == "ca_cert.pem" or not name.endswith("_cert.pem"):
continue
path = os.path.join(ca_dir, name)
if os.path.isfile(path):
with open(path, "rb") as f:
parts.append(f.read())
with open(bundle_path, "wb") as f:
f.write(b"".join(parts))
def make_ca(ca_dir, ca_name, days=3650, issuing_ca=None, aia_base_url=None):
@@ -119,7 +110,7 @@ def make_ca(ca_dir, ca_name, days=3650, issuing_ca=None, aia_base_url=None):
_err("Failed to generate CA certificate and key.")
return False
make_hash_link(root_ca_cert_path)
_rebuild_ca_bundle(ca_dir)
if aia_base_url:
with open(aia_file, "w") as f:
@@ -157,7 +148,7 @@ def make_ca(ca_dir, ca_name, days=3650, issuing_ca=None, aia_base_url=None):
_err("Failed to generate issuing CA certificate and key.")
return False
make_hash_link(ca_cert_path)
_rebuild_ca_bundle(ca_dir)
if aia_base_url:
with open(aia_file, "w") as f:

View File

@@ -22,19 +22,19 @@
# These functions require Bash and OpenSSL to be installed on the system.
function make_hash_link() {
local CERT_PATH="$1"
if [[ ! -f "$CERT_PATH" ]]; then
echo "ERROR: Certificate file $CERT_PATH does not exist." >&2
return 1
function _rebuild_ca_bundle() {
local CA_DIR="$1"
local BUNDLE="$CA_DIR/ca_bundle.pem"
: > "$BUNDLE"
if [[ -f "$CA_DIR/ca_cert.pem" ]]; then
cat "$CA_DIR/ca_cert.pem" >> "$BUNDLE"
fi
local CERT_DIR="$(dirname "$CERT_PATH")"
local HASH="$(openssl x509 -in "$CERT_PATH" -noout -hash 2>/dev/null)"
if [[ -z "$HASH" ]]; then
echo "ERROR: Failed to calculate hash for certificate $CERT_PATH." >&2
return 1
fi
ln -sf "$(basename "$CERT_PATH")" "$CERT_DIR/${HASH}.0"
local f
for f in "$CA_DIR"/*_cert.pem; do
[[ -f "$f" ]] || continue
[[ "$(basename "$f")" == "ca_cert.pem" ]] && continue
cat "$f" >> "$BUNDLE"
done
}
function make_ca() {
@@ -129,8 +129,8 @@ function make_ca() {
return 1
fi
# Make a "hash" symlink for the CA certificate to allow OpenSSL to find it when verifying other certificates
make_hash_link "$CA_DIR/$ROOT_CA_CERT"
# Rebuild the CA bundle (root + any issuing CAs) for use with `openssl verify -CAfile`
_rebuild_ca_bundle "$CA_DIR"
if [[ -n "$AIA_BASE_URL" ]]; then
echo "$AIA_BASE_URL" > "$CA_DIR/aia_base_url.txt"
@@ -162,8 +162,8 @@ function make_ca() {
fi
fi
# Make a "hash" symlink for the issuing CA certificate to allow OpenSSL to find it when verifying other certificates
make_hash_link "$CA_DIR/${CA_CERT}"
# Rebuild the CA bundle (root + any issuing CAs) for use with `openssl verify -CAfile`
_rebuild_ca_bundle "$CA_DIR"
if [[ -n "$AIA_BASE_URL" ]]; then
echo "$AIA_BASE_URL" > "$CA_DIR/aia_base_url.txt"