feat: Enhance certificate generation with user type support and additional SANs
/ test-shell (push) Successful in 17s

This commit is contained in:
2026-05-25 06:02:02 +02:00
parent e8b5241a54
commit ad6af575dc
2 changed files with 111 additions and 34 deletions
+44 -8
View File
@@ -41,6 +41,13 @@ reset_dirs() {
SIMPLE_CA_DIR="" SIMPLE_CA_DIR=""
} }
assert_file() {
if [[ ! -f "$1" ]]; then
echo "ERROR: expected file not found: $1" >&2
exit 1
fi
}
verify_cert() { verify_cert() {
local CERT_PATH="$1" local CERT_PATH="$1"
if ! openssl verify -CAfile "$CA_DIR/ca_bundle.pem" "$CERT_PATH" 2>/dev/null; then if ! openssl verify -CAfile "$CA_DIR/ca_bundle.pem" "$CERT_PATH" 2>/dev/null; then
@@ -50,6 +57,18 @@ verify_cert() {
echo "Verified: $CERT_PATH" echo "Verified: $CERT_PATH"
} }
assert_eku() {
local CERT_PATH="$1"
local EKU="$2"
local TEXT
TEXT="$(openssl x509 -in "$CERT_PATH" -noout -text 2>/dev/null)"
if ! echo "$TEXT" | grep -q "$EKU"; then
echo "ERROR: EKU '$EKU' not found in $CERT_PATH" >&2
exit 1
fi
echo "EKU OK: $EKU"
}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Standalone CA — certs issued by root CA go into CA_DIR # Standalone CA — certs issued by root CA go into CA_DIR
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -58,12 +77,12 @@ echo
echo "--- [shell] Standalone CA ---" echo "--- [shell] Standalone CA ---"
reset_dirs reset_dirs
make_ca --ca-dir "$CA_DIR" "Test CA" 2>/dev/null make_ca --ca-dir "$CA_DIR" "Test CA" 2>/dev/null
[[ -f "$CA_DIR/ca_cert.pem" ]] || { echo "ERROR: ca_cert.pem not created" >&2; exit 1; } assert_file "$CA_DIR/ca_cert.pem"
[[ -f "$CA_DIR/ca_bundle.pem" ]] || { echo "ERROR: ca_bundle.pem not created" >&2; exit 1; } assert_file "$CA_DIR/ca_bundle.pem"
verify_cert "$CA_DIR/ca_cert.pem" verify_cert "$CA_DIR/ca_cert.pem"
make_cert "test" "test.example.com" "127.0.0.1" 2>/dev/null make_cert "test" "test.example.com" "127.0.0.1" 2>/dev/null
[[ -f "$CA_DIR/test_cert.pem" ]] || { echo "ERROR: test_cert.pem not created in CA_DIR" >&2; exit 1; } assert_file "$CA_DIR/test_cert.pem"
verify_cert "$CA_DIR/test_cert.pem" verify_cert "$CA_DIR/test_cert.pem"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -77,18 +96,35 @@ make_ca --ca-dir "$CA_DIR" "Test Root CA" 2>/dev/null
verify_cert "$CA_DIR/ca_cert.pem" verify_cert "$CA_DIR/ca_cert.pem"
make_ca --issuing-ca "issuing_ca" "Issuing CA" 2>/dev/null make_ca --issuing-ca "issuing_ca" "Issuing CA" 2>/dev/null
[[ -f "$CA_DIR/issuing_ca/ca_cert.pem" ]] || { echo "ERROR: issuing_ca/ca_cert.pem not created" >&2; exit 1; } assert_file "$CA_DIR/issuing_ca/ca_cert.pem"
verify_cert "$CA_DIR/issuing_ca/ca_cert.pem" verify_cert "$CA_DIR/issuing_ca/ca_cert.pem"
make_cert --issuing-ca "issuing_ca" "test" "test.example.com" "127.0.0.1" 2>/dev/null make_cert --issuing-ca "issuing_ca" "test" "test.example.com" "127.0.0.1" 2>/dev/null
[[ -f "$CA_DIR/issuing_ca/test_cert.pem" ]] || { echo "ERROR: issuing_ca/test_cert.pem not created" >&2; exit 1; } assert_file "$CA_DIR/issuing_ca/test_cert.pem"
verify_cert "$CA_DIR/issuing_ca/test_cert.pem" verify_cert "$CA_DIR/issuing_ca/test_cert.pem"
assert_eku "$CA_DIR/issuing_ca/test_cert.pem" "TLS Web Server Authentication"
assert_eku "$CA_DIR/issuing_ca/test_cert.pem" "TLS Web Client Authentication"
make_pfx --issuing-ca "issuing_ca" --password "s3cr3t" "$CA_DIR/issuing_ca/test_cert.pem" 2>/dev/null make_pfx --issuing-ca "issuing_ca" --password "s3cr3t" "$CA_DIR/issuing_ca/test_cert.pem" 2>/dev/null
[[ -f "$CA_DIR/issuing_ca/test.pfx" ]] || { echo "ERROR: issuing_ca/test.pfx not created" >&2; exit 1; } assert_file "$CA_DIR/issuing_ca/test.pfx"
openssl pkcs12 -in "$CA_DIR/issuing_ca/test.pfx" -noout -info -password pass:"s3cr3t" 2>/dev/null \ if ! openssl pkcs12 -in "$CA_DIR/issuing_ca/test.pfx" -noout -info -password pass:"s3cr3t" 2>/dev/null; then
|| { echo "ERROR: PFX verification failed" >&2; exit 1; } echo "ERROR: PFX verification failed" >&2
exit 1
fi
echo "PFX: OK" echo "PFX: OK"
# ---------------------------------------------------------------------------
# User certificate
# ---------------------------------------------------------------------------
echo
echo "--- [shell] User certificate ---"
make_cert --issuing-ca "issuing_ca" --type user "Alice Example" "alice@example.com" 2>/dev/null
assert_file "$CA_DIR/issuing_ca/Alice Example_cert.pem"
verify_cert "$CA_DIR/issuing_ca/Alice Example_cert.pem"
assert_eku "$CA_DIR/issuing_ca/Alice Example_cert.pem" "TLS Web Client Authentication"
assert_eku "$CA_DIR/issuing_ca/Alice Example_cert.pem" "E-mail Protection"
assert_eku "$CA_DIR/issuing_ca/Alice Example_cert.pem" "Code Signing"
echo echo
echo "All shell tests passed." echo "All shell tests passed."
+67 -26
View File
@@ -57,8 +57,9 @@ _require_ca_dir() {
fi fi
} }
_is_ip() { [[ "$1" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; } _is_ip() { [[ "$1" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; }
_is_dns() { [[ "$1" =~ ^[a-z0-9-]+(\.[a-z0-9-]+)*$ ]]; } _is_dns() { [[ "$1" =~ ^[a-z0-9-]+(\.[a-z0-9-]+)*$ ]]; }
_is_email() { [[ "$1" =~ ^[^@]+@[^@]+\.[^@]+$ ]]; }
make_ca() { make_ca() {
local CA_DAYS=3650 local CA_DAYS=3650
@@ -187,6 +188,7 @@ make_cert() {
local ISSUING_CA="" local ISSUING_CA=""
local CERT_DAYS=365 local CERT_DAYS=365
local CERT_DIR="" local CERT_DIR=""
local CERT_TYPE="server"
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
@@ -218,6 +220,16 @@ make_cert() {
return 1 return 1
fi fi
CERT_DAYS="$2"; shift 2 ;; CERT_DAYS="$2"; shift 2 ;;
--type)
if [[ -z "$2" ]]; then
echo "ERROR: --type requires a value." >&2
return 1
fi
if [[ "$2" != "server" && "$2" != "user" ]]; then
echo "ERROR: --type must be 'server' or 'user'." >&2
return 1
fi
CERT_TYPE="$2"; shift 2 ;;
*) break ;; *) break ;;
esac esac
done done
@@ -233,7 +245,7 @@ make_cert() {
echo "ERROR: Subject name is required." >&2 echo "ERROR: Subject name is required." >&2
return 1 return 1
fi fi
if ! _is_dns "$CERT_SUBJECT_NAME"; then if [[ "$CERT_TYPE" == "server" ]] && ! _is_dns "$CERT_SUBJECT_NAME"; then
echo "ERROR: Invalid subject name '$CERT_SUBJECT_NAME'. Must be a valid DNS name." >&2 echo "ERROR: Invalid subject name '$CERT_SUBJECT_NAME'. Must be a valid DNS name." >&2
return 1 return 1
fi fi
@@ -256,11 +268,17 @@ make_cert() {
fi fi
local CERT_NAME="${CERT_SUBJECT_NAME%%.*}" local CERT_NAME="${CERT_SUBJECT_NAME%%.*}"
local SANS=("DNS:${CERT_SUBJECT_NAME}") local SANS=()
if [[ "$CERT_TYPE" == "server" ]]; then
SANS=("DNS:${CERT_SUBJECT_NAME}")
fi
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
if _is_ip "$1"; then if _is_ip "$1"; then
SANS+=("IP:$1") SANS+=("IP:$1")
elif _is_email "$1"; then
SANS+=("email:$1")
elif _is_dns "$1"; then elif _is_dns "$1"; then
SANS+=("DNS:$1") SANS+=("DNS:$1")
else else
@@ -270,7 +288,11 @@ make_cert() {
shift shift
done done
echo "Generating server certificate for '$CERT_SUBJECT_NAME' with SANs:" if [[ "$CERT_TYPE" == "server" ]]; then
echo "Generating server certificate for '$CERT_SUBJECT_NAME' with SANs:"
else
echo "Generating user certificate for '$CERT_SUBJECT_NAME':"
fi
for san in "${SANS[@]}"; do echo " - $san"; done for san in "${SANS[@]}"; do echo " - $san"; done
if [[ -f "$CERT_DIR/${CERT_NAME}_cert.pem" && -f "$CERT_DIR/${CERT_NAME}_key.pem" ]]; then if [[ -f "$CERT_DIR/${CERT_NAME}_cert.pem" && -f "$CERT_DIR/${CERT_NAME}_key.pem" ]]; then
@@ -278,28 +300,47 @@ make_cert() {
return 0 return 0
fi fi
local SANS_EXT="subjectAltName=$(IFS=,; echo "${SANS[*]}")" local REQ_ARGS=(
-newkey rsa:4096
-keyout "$CERT_DIR/${CERT_NAME}_key.pem"
-noenc
-subj "/CN=${CERT_SUBJECT_NAME}"
-addext "basicConstraints=critical,CA:FALSE"
)
echo "Generating server certificate and key..." local X509_ARGS=(
if ! openssl req \ -req
-newkey rsa:4096 \ -CA "$SIGNING_CERT"
-keyout "$CERT_DIR/${CERT_NAME}_key.pem" \ -CAkey "$SIGNING_KEY"
-noenc \ -copy_extensions copyall
-subj "/CN=${CERT_SUBJECT_NAME}" \ -days "$CERT_DAYS"
-addext "basicConstraints=critical,CA:FALSE" \ -text
-addext "keyUsage=critical,digitalSignature,keyEncipherment" \ -out "$CERT_DIR/${CERT_NAME}_cert.pem"
-addext "extendedKeyUsage=serverAuth,clientAuth" \ )
-addext "$SANS_EXT" \
${AIA_URL:+-addext "authorityInfoAccess=caIssuers;URI:${AIA_URL}"} \ if [[ "$CERT_TYPE" == "server" ]]; then
| openssl x509 \ REQ_ARGS+=(
-req \ -addext "keyUsage=critical,digitalSignature,keyEncipherment"
-CA "$SIGNING_CERT" \ -addext "extendedKeyUsage=serverAuth,clientAuth"
-CAkey "$SIGNING_KEY" \ -addext "subjectAltName=$(IFS=,; echo "${SANS[*]}")"
-copy_extensions copyall \ )
-days "$CERT_DAYS" \ else
-text \ REQ_ARGS+=(
-out "$CERT_DIR/${CERT_NAME}_cert.pem"; then -addext "keyUsage=critical,digitalSignature,nonRepudiation"
echo "ERROR: Failed to generate server certificate and key." >&2 -addext "extendedKeyUsage=clientAuth,emailProtection,codeSigning"
)
if [[ ${#SANS[@]} -gt 0 ]]; then
REQ_ARGS+=(-addext "subjectAltName=$(IFS=,; echo "${SANS[*]}")")
fi
fi
if [[ -n "$AIA_URL" ]]; then
REQ_ARGS+=(-addext "authorityInfoAccess=caIssuers;URI:${AIA_URL}")
fi
echo "Generating certificate and key..."
if ! openssl req "${REQ_ARGS[@]}" | openssl x509 "${X509_ARGS[@]}"; then
echo "ERROR: Failed to generate certificate and key." >&2
return 1 return 1
fi fi
} }