222 lines
6.4 KiB
Bash
222 lines
6.4 KiB
Bash
#!/usr/bin/env bash
|
|
# Simple local CA helper for issuing server certs with SANs, verifying, and optional nginx deploy.
|
|
set -euo pipefail
|
|
|
|
MYCA_DIR="${MYCA_DIR:-/etc/ssl/myca}"
|
|
CERT_DIR="$MYCA_DIR/certs"
|
|
KEY_DIR="$MYCA_DIR/private"
|
|
CSR_DIR="$MYCA_DIR/csrs"
|
|
EXT_DIR="$MYCA_DIR/exts"
|
|
NGINX_SSL_DIR="${NGINX_SSL_DIR:-/etc/nginx/ssl}"
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
myca.sh COMMAND [args]
|
|
|
|
Commands:
|
|
issue NAME [--dns foo --ip 1.2.3.4 ...] [--days N] [--reuse-key] [--deploy-nginx]
|
|
Create key + CSR + SAN ext, sign with CA, write cert.
|
|
sign NAME --csr /path/to/file.csr [--dns ... --ip ...] [--days N]
|
|
Sign an existing CSR with SANs.
|
|
show NAME|/path/to/cert.crt
|
|
Show subject, SANs, validity.
|
|
verify NAME|/path/to/cert.crt
|
|
Verify against CA.
|
|
list
|
|
List issued certs.
|
|
trust-ca
|
|
Install CA into Debian or Ubuntu trust store and update.
|
|
|
|
Environment:
|
|
MYCA_DIR Default /etc/ssl/myca
|
|
NGINX_SSL_DIR Default /etc/nginx/ssl
|
|
|
|
Examples:
|
|
myca.sh issue snipe-it.lan --dns snipe-it.lan --ip 192.168.17.51 --deploy-nginx
|
|
myca.sh show snipe-it.lan
|
|
myca.sh verify snipe-it.lan
|
|
|
|
Notes:
|
|
- CA files expected: $MYCA_DIR/myCA.pem and $MYCA_DIR/myCA.key
|
|
- Avoid .local hostnames. Use .lan or a domain you control.
|
|
USAGE
|
|
}
|
|
|
|
ensure_dirs() {
|
|
install -d -m 755 "$CERT_DIR" "$CSR_DIR" "$EXT_DIR"
|
|
install -d -m 700 "$KEY_DIR"
|
|
}
|
|
|
|
have_ca() {
|
|
[[ -s "$MYCA_DIR/myCA.pem" && -s "$MYCA_DIR/myCA.key" ]] || {
|
|
echo "Missing CA: $MYCA_DIR/myCA.pem or myCA.key" >&2; exit 1;
|
|
}
|
|
}
|
|
|
|
make_ext() {
|
|
local name="$1"; shift
|
|
local dns_arr=() ip_arr=()
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--dns) dns_arr+=("$2"); shift 2;;
|
|
--ip) ip_arr+=("$2"); shift 2;;
|
|
*) echo "Unknown ext arg: $1" >&2; exit 2;;
|
|
case_esac
|
|
esac
|
|
done
|
|
|
|
local has_cn=0
|
|
for d in "${dns_arr[@]:-}"; do [[ "$d" == "$name" ]] && has_cn=1; done
|
|
[[ $has_cn -eq 0 ]] && dns_arr=("$name" "${dns_arr[@]:-}")
|
|
|
|
{
|
|
echo "basicConstraints=CA:FALSE"
|
|
echo "keyUsage=digitalSignature,keyEncipherment"
|
|
echo "extendedKeyUsage=serverAuth"
|
|
echo "subjectAltName=@alt_names"
|
|
echo "[alt_names]"
|
|
local i=1
|
|
for d in "${dns_arr[@]:-}"; do echo "DNS.$i=$d"; i=$((i+1)); done
|
|
i=1
|
|
for ip in "${ip_arr[@]:-}"; do echo "IP.$i=$ip"; i=$((i+1)); done
|
|
} > "$EXT_DIR/$name.ext"
|
|
}
|
|
|
|
gen_key_csr() {
|
|
local name="$1"
|
|
openssl genrsa -out "$KEY_DIR/$name.key" 2048 >/dev/null 2>&1
|
|
chmod 600 "$KEY_DIR/$name.key"
|
|
openssl req -new -key "$KEY_DIR/$name.key" \
|
|
-out "$CSR_DIR/$name.csr" -subj "/CN=$name" >/dev/null 2>&1
|
|
}
|
|
|
|
sign_csr() {
|
|
local name="$1" days="$2"
|
|
local serial_flag
|
|
if [[ -s "$MYCA_DIR/myCA.srl" ]]; then
|
|
serial_flag=(-CAserial "$MYCA_DIR/myCA.srl")
|
|
else
|
|
serial_flag=(-CAcreateserial)
|
|
fi
|
|
openssl x509 -req -in "$CSR_DIR/$name.csr" \
|
|
-CA "$MYCA_DIR/myCA.pem" -CAkey "$MYCA_DIR/myCA.key" \
|
|
"${serial_flag[@]}" -out "$CERT_DIR/$name.crt" -days "$days" -sha256 \
|
|
-extfile "$EXT_DIR/$name.ext" >/dev/null 2>&1
|
|
chmod 644 "$CERT_DIR/$name.crt"
|
|
}
|
|
|
|
deploy_nginx() {
|
|
local name="$1"
|
|
install -d -m 755 "$NGINX_SSL_DIR"
|
|
install -m 644 "$CERT_DIR/$name.crt" "$NGINX_SSL_DIR/$name.crt"
|
|
install -m 600 "$KEY_DIR/$name.key" "$NGINX_SSL_DIR/$name.key"
|
|
if command -v nginx >/dev/null 2>&1; then
|
|
nginx -t && systemctl reload nginx || true
|
|
fi
|
|
echo "Deployed to $NGINX_SSL_DIR/$name.{crt,key}"
|
|
}
|
|
|
|
cmd_issue() {
|
|
local name days=825 reuse_key=0 deploy=0
|
|
local dns_args=() ip_args=()
|
|
shift
|
|
[[ $# -ge 1 ]] || { usage; exit 2; }
|
|
name="$1"; shift
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--days) days="$2"; shift 2;;
|
|
--reuse-key) reuse_key=1; shift;;
|
|
--deploy-nginx) deploy=1; shift;;
|
|
--dns) dns_args+=("$2"); shift 2;;
|
|
--ip) ip_args+=("$2"); shift 2;;
|
|
*) echo "Unknown option: $1" >&2; exit 2;;
|
|
esac
|
|
done
|
|
|
|
ensure_dirs; have_ca
|
|
make_ext "$name" $(for d in "${dns_args[@]:-}"; do printf -- " --dns %q" "$d"; done) \
|
|
$(for i in "${ip_args[@]:-}"; do printf -- " --ip %q" "$i"; done)
|
|
|
|
if [[ $reuse_key -eq 0 || ! -s "$KEY_DIR/$name.key" ]]; then
|
|
gen_key_csr "$name"
|
|
else
|
|
openssl req -new -key "$KEY_DIR/$name.key" \
|
|
-out "$CSR_DIR/$name.csr" -subj "/CN=$name" >/dev/null 2>&1
|
|
fi
|
|
|
|
sign_csr "$name" "$days"
|
|
echo "Issued: $CERT_DIR/$name.crt"
|
|
[[ $deploy -eq 1 ]] && deploy_nginx "$name"
|
|
}
|
|
|
|
cmd_sign_existing() {
|
|
local name csr_path="" days=825
|
|
local dns_args=() ip_args=()
|
|
shift
|
|
[[ $# -ge 1 ]] || { usage; exit 2; }
|
|
name="$1"; shift
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--csr) csr_path="$2"; shift 2;;
|
|
--days) days="$2"; shift 2;;
|
|
--dns) dns_args+=("$2"); shift 2;;
|
|
--ip) ip_args+=("$2"); shift 2;;
|
|
*) echo "Unknown option: $1" >&2; exit 2;;
|
|
esac
|
|
done
|
|
[[ -n "$csr_path" && -s "$csr_path" ]] || { echo "CSR not found" >&2; exit 1; }
|
|
ensure_dirs; have_ca
|
|
cp -f "$csr_path" "$CSR_DIR/$name.csr"
|
|
make_ext "$name" $(for d in "${dns_args[@]:-}"; do printf -- " --dns %q" "$d"; done) \
|
|
$(for i in "${ip_args[@]:-}"; do printf -- " --ip %q" "$i"; done)
|
|
sign_csr "$name" "$days"
|
|
echo "Signed: $CERT_DIR/$name.crt"
|
|
}
|
|
|
|
cmd_show() {
|
|
local target="${1:-}"
|
|
[[ -n "$target" ]] || { usage; exit 2; }
|
|
[[ "$target" == *.crt || "$target" == *.pem ]] || target="$CERT_DIR/$target.crt"
|
|
[[ -s "$target" ]] || { echo "Cert not found: $target" >&2; exit 1; }
|
|
openssl x509 -in "$target" -noout -subject -issuer -dates
|
|
echo "SANs:"
|
|
openssl x509 -in "$target" -noout -text | awk '/Subject Alternative Name/{f=1;next}/X509v3/{f=0}f'
|
|
}
|
|
|
|
cmd_verify() {
|
|
local target="${1:-}"
|
|
[[ -n "$target" ]] || { usage; exit 2; }
|
|
[[ "$target" == *.crt || "$target" == *.pem ]] || target="$CERT_DIR/$target.crt"
|
|
[[ -s "$target" ]] || { echo "Cert not found: $target" >&2; exit 1; }
|
|
have_ca
|
|
openssl verify -CAfile "$MYCA_DIR/myCA.pem" "$target"
|
|
}
|
|
|
|
cmd_list() {
|
|
ensure_dirs
|
|
ls -1 "$CERT_DIR"/*.crt 2>/dev/null || true
|
|
}
|
|
|
|
cmd_trust_ca() {
|
|
have_ca
|
|
install -m 644 "$MYCA_DIR/myCA.pem" /usr/local/share/ca-certificates/myCA.crt
|
|
update-ca-certificates
|
|
echo "CA installed to system trust."
|
|
}
|
|
|
|
main() {
|
|
[[ $# -ge 1 ]] || { usage; exit 2; }
|
|
case "$1" in
|
|
issue) cmd_issue "$@";;
|
|
sign) cmd_sign_existing "$@";;
|
|
show) shift; cmd_show "${1:-}";;
|
|
verify) shift; cmd_verify "${1:-}";;
|
|
list) cmd_list;;
|
|
trust-ca) cmd_trust_ca;;
|
|
-h|--help|help) usage;;
|
|
*) echo "Unknown command: $1" >&2; usage; exit 2;;
|
|
esac
|
|
}
|
|
main "$@"
|
|
|