aboutsummaryrefslogtreecommitdiffstats
path: root/package/sbin/painless-le
blob: 59ac735ba32fb27d83e15b9dcbc85e71f3fb200f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/bin/bash
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
# PainlessLE – A wrapper script for Certbot  [Thomas Lange <code@nerdmind.de>] #
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
#                                                                              #
# Easily get an X.509 certificate from the Let's Encrypt Certificate Authority #
# without the need of having a dedicated web server installed. With PainlessLE #
# you'll manage your certificate and private key files by yourself. Instead of #
# Certbot, you're responsible for the periodic renewal of the certificates.    #
#                                                                              #
# USAGE:                                                                       #
# painless-le [OPTIONS] TARGET_DIR DNS_DOMAIN [DNS_DOMAIN ...]                 #
#                                                                              #
# TARGET_DIR: Path to the target directory for the certificate files.          #
# DNS_DOMAIN: One or more DNS hostnames to include in the certficate.          #
#                                                                              #
# [-K name]: Filename of the existing private key in target directory.         #
# [-I name]: Filename for the intermediate certificate in target directory.    #
# [-C name]: Filename for the standalone certificate in target directory.      #
# [-F name]: Filename for the certificate+intermediate in target directory.    #
# [--server URL]: Specify a custom URL to an ACME endpoint.                    #
# [--staging]: Use a staging server to obtain an invalid test certificate.     #
#                                                                              #
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#

#===============================================================================
# Normalize command-line argument string
#===============================================================================
eval set -- "$(getopt -o K:I:C:F: -l server:,staging -- "$@")"

#===============================================================================
# Parse command-line options
#===============================================================================
while true; do
	case "$1" in
		-K) OPT_CONFIDENTIAL="$2"; shift 2;;
		-I) OPT_INTERMEDIATE="$2"; shift 2;;
		-C) OPT_CERTIFICATE_ONLY="$2"; shift 2;;
		-F) OPT_CERTIFICATE_FULL="$2"; shift 2;;
		--server) OPT_SERVER="$2"; shift 2;;
		--staging) OPT_STAGING=1; shift;;
		--) shift; break;;
	esac
done

#===============================================================================
# Set positional argument variables
#===============================================================================
TARGET_DIR="$1"
DNS_DOMAIN="${@:2}"

#===============================================================================
# Check if required positional arguments are missing
#===============================================================================
[ -z "${TARGET_DIR}" ] && echo "$0: Missing argument: TARGET_DIR" >&2
[ -z "${DNS_DOMAIN}" ] && echo "$0: Missing argument: DNS_DOMAIN" >&2
[ -z "${TARGET_DIR}" ] || [ -z "${DNS_DOMAIN}" ] && exit 1

#===============================================================================
# Define filename variables
#===============================================================================
     REQUESTFILE="$(mktemp /tmp/painless-le.XXXXXX.csr)"
    CONFIDENTIAL="${TARGET_DIR%/}/${OPT_CONFIDENTIAL:-confidential.pem}"
    INTERMEDIATE="${TARGET_DIR%/}/${OPT_INTERMEDIATE:-intermediate.pem}"
CERTIFICATE_ONLY="${TARGET_DIR%/}/${OPT_CERTIFICATE_ONLY:-certificate_only.pem}"
CERTIFICATE_FULL="${TARGET_DIR%/}/${OPT_CERTIFICATE_FULL:-certificate_full.pem}"

#===============================================================================
# Ensure the Certificate-Signing-Request file is deleted on exit
#===============================================================================
trap 'rm ${REQUESTFILE}' EXIT

#===============================================================================
# Assemble OpenSSL configuration for CSR generation
#===============================================================================
SUBJECT_ALT_NAME="DNS:$(echo ${DNS_DOMAIN} | sed "s/ /,DNS:/g")"
OPENSSL_CONFIG="[req]
distinguished_name = req_distinguished_name
[req_distinguished_name]
[SAN]
subjectAltName=${SUBJECT_ALT_NAME}"

#===============================================================================
# Create Certificate-Signing-Request
#===============================================================================
openssl req -config <(echo "$OPENSSL_CONFIG") -new -sha256 -reqexts SAN \
	-subj "/" -key "${CONFIDENTIAL}" -out "${REQUESTFILE}"

#===============================================================================
# Check if Certificate-Signing-Request creation failed
#===============================================================================
if [ $? != 0 ]; then
	echo "$0: Certificate-Signing-Request (CSR) could not be created!" >&2
	exit 1
fi

#===============================================================================
# Run Certbot to obtain the certificate
#===============================================================================
CERTBOT_OPTIONS=(
	"--csr" "${REQUESTFILE}"
	"--cert-path" "${CERTIFICATE_ONLY}.$$"
	"--chain-path" "${INTERMEDIATE}.$$"
	"--fullchain-path" "${CERTIFICATE_FULL}.$$"
)

[ ! -z "$OPT_STAGING" ] && \
	CERTBOT_OPTIONS+=("--staging")

[ ! -z "$OPT_SERVER" ] && [ -z "$OPT_STAGING" ] && \
	CERTBOT_OPTIONS+=("--server" "${OPT_SERVER}")

certbot certonly --authenticator standalone "${CERTBOT_OPTIONS[@]}"

#===============================================================================
# Check if Certbot failed to obtain a certificate
#===============================================================================
if [ $? != 0 ]; then
	echo "$0: Certbot could not successfully accomplish the ACME challenge." >&2
	exit 1
fi

#===============================================================================
# Replace old certificate files (if any) with the newly obtained ones
#===============================================================================
for file in "${INTERMEDIATE}" "${CERTIFICATE_ONLY}" "${CERTIFICATE_FULL}"; do
	if [ -f "${file}.$$" ]; then
		mv "${file}.$$" "${file}"
	fi
done

#===============================================================================
# Inherit permissions of private key to new certificate files
#===============================================================================
for command in "chmod" "chown"; do
	$command --reference "${CONFIDENTIAL}" \
		"${INTERMEDIATE}" "${CERTIFICATE_ONLY}" "${CERTIFICATE_FULL}"
done