summaryrefslogblamecommitdiffstats
path: root/satellit_installer/static_files/lighttpd/opt/openslx/slx-cert
blob: 3409244f76d03ed56c843a9ec365a8bf5a7f2456 (plain) (tree)
1
2
3
4
5
6
7
8
9



                                    

                                                         


                                   
                                               

                                   


                                     
 
                                      

                                    

                                                

                                                                         











                                                                         


                               
                             

                                                                        
                                                      


                                                                                           








                                                             


                                                                  
           



                                                             
                                    
                                                               

                     

                                                                         

                             

                              

 
                                           
























                                                    















                                                                     
        


                                           
                               






                                         
                                       
                                                    
                                     

                        








                                                                                                           
                     




                                                                                  


      

                                                                     
                                 

                                                                                    
                                                                                              






                                                                                      
               

  
                              
                                                                                  

                                                                                                    

                                               

                                                           

                                                      
                                       
                                                   
                                
                                                             

                                                                                
                                 


                                                                                                     
                                                   
         


                                  















                                                                      
 


                                            
                               














                                                                                                           

                        














                                                                  

    






















                                                                               


                                                            

                                                                                          
                                                                                               







                                                              
                   



                                                                 
                                                             







                                                              






                                                                                                                
                           
                                    
                                                                                


                                          
                                       
 
                                                               
                                           

                                                                                                                       
 

                                           
 
                                                   






                                                  
#!/bin/bash

# OpenSLX SSL Certificate management

mkdir -p "/run/openslx"
if ! mkdir "/run/openslx/cert-manager" 2> /dev/null; then
	echo "Already in progress."
	exit 1
fi
trap 'rm -rf -- /run/openslx/cert-manager' EXIT

declare -rg BASE="/etc/ssl/openslx"
declare -rg PRIVDIR="$BASE/private"
declare -rg CERTDIR="$BASE/cert"
declare -rg LIGHTDIR="$BASE/lighttpd"

mkdir -p "$BASE" "$PRIVDIR" "$CERTDIR"

chown -R root:root "$BASE" || exit 1
chmod u+rwx,go+rx-w "$BASE" "$CERTDIR" || exit 1
chmod u+rwx,go-rwx "$PRIVDIR" || exit 1
# Before doing anything, make sure we have a CA with enough validity left
# File name format for ca is:
# ${PRIVDIR}/ca-TTTTTTTTTT.key
# ${CERTDIR}/ca-TTTTTTTTTT.crt
# Where TT is the unix timestamp of "validTo" of that cert.
#
# A new CA will be generated when the previous has less than two years
# of validity left.
# We deliver a new CA to clients immediately when it was generated, but
# keep signing server certs with the old one, until the old one only
# has a year of validity left, then we switch to the next-oldest CA-cert.
# CA certs are valid for 10 years, server certs for 1 year.
# This should allow for clients to have two years of uptime without any
# problems due to cert rollover.

declare -rg NOW="$( date +%s )"
# PROD
declare -rg srv_days=365 # 1y
declare -rg srv_s="$(( srv_days * 86400 ))"
declare -rg srv_min_remain_s="$(( srv_days / 2 * 86400 ))" # half a year
declare -rg srv_new_ts="$(( srv_days * 86400 + NOW ))"
declare -rg ca_days="$(( 10 * 365 ))" # 10y
declare -rg ca_min_remain_s="$(( 2 * srv_days * 86400 ))" # 2y, twice that of a server cert
declare -rg ca_new_expire_ts="$(( ca_days * 86400 + NOW ))"
# TEST
#declare -rg ca_days=1825 # 5y
#declare -rg ca_min_remain_s="$(( 1260 ))" # bit more than 1y
#declare -rg ca_new_expire_ts="$(( 1320 + NOW ))"
#declare -rg srv_days=365 # 1y
#declare -rg srv_min_remain_s="$(( 1200 ))" # half a year
#declare -rg srv_new_ts="$(( 1230 + NOW ))"


# Extract timestamp from given filename.
# Filename format has to be ca-UUUUUUUUU[.-]* or srv-UUUUUUUU[.-]*
# Where UUUUU is a unix timestamp
get_ts () {
	ts="${1%.*}" # Remove everything from last dot on
	ts="${ts##*/ca-}" # remove ca- prefix
	ts="${ts##*/srv-}" # remove srv- prefix
	from="${ts%-*}" # Remove everything from last dash on
	if [ "$from" = "$ts" ]; then
		# There was no dash, we have a normal timestamp
		from=
	else
		# There was a dash - $from is now the start of the range,
		# $ts is the end of the range
		ts="${ts#*-}"
	fi
	# Set the return value
	(( ts > 1234567890 ))
}

# Create an openssl config for signing CSRs
create_conf () {
	ca_dir="$( mktemp -d /tmp/bwlp-XXXXXXXX )"
	[ -z "$ca_dir" ] && exit 1
	mkdir "$ca_dir"/{certs,crl,newcerts,private}
	touch "$ca_dir"/index.txt
	ca_config="$ca_dir/openssl.cnf"
	cp -f "/etc/ssl/openssl.cnf" "$ca_config"
	cat >> "$ca_config" <<-MYCA
		[ CA_openslx ]
		dir		= $ca_dir
		certs		= \$dir/certs
		crl_dir		= \$dir/crl
		database	= \$dir/index.txt
		new_certs_dir	= \$dir/newcerts
		serial		= \$dir/serial
		crl		= \$dir/crl.pem
		x509_extensions	= usr_cert
		name_opt 	= ca_default
		cert_opt 	= ca_default
		default_md	= default
		preserve	= no
		policy		= policy_match
	MYCA
}

declare -ag cmdargs=( "$@" )
maybe_restart () {
	for i in "${cmdargs[@]}"; do
		[ "$i" = "--restart" ] && exit 13
	done
	echo "Restating script, wiping everything except CAs..."
	rm -f -- /etc/ssl/openslx/*/{srv,int}*
	exec "$0" "${cmdargs[@]}" --restart
	exit 14
}

# Start

# Check if existing CA is still valid for long enough
# Globbing is sorted so should make sure we check the newest one last
declare -a ca_list=()
ca_last=
latest_ca_cert=
oldest_ca_ts=
for i in "${PRIVDIR}"/ca-??????????.key; do
	[ -s "$i" ] || continue
	if ! get_ts "$i"; then
		echo "Invalid ca-key: $i"
		rm -f -- "$i"
		continue
	fi
	cert="${CERTDIR}/ca-${ts}.crt"
	if ! [ -s "$cert" ] \
		|| (( ts < NOW )); then
		# Missing cert, or expired -> delete
		rm -f -- "$cert" "$i"
		continue
	fi
	# Check if key and cert match
	if [ "$( openssl x509 -in "$cert" -noout -pubkey )" != "$( openssl pkey -in "$i" -pubout )" ]; then
		echo "Publickey in cert doesn't match publickey in keypair: $cert + $i"
		rm -f -- "$cert" "$i"
		continue
	fi
	# All CAs still valid
	ca_list+=( "$cert" )
	# Latest CA
	ca_last="$ts"
	latest_ca_cert="$cert"
	# Oldest one that is still valid for as long as we want to sign a new cert
	if [ -z "$oldest_ca_ts" ] && (( ts > NOW + srv_s )); then
		oldest_ca_ts="$ts"
	fi
done

mknew=
if [ -z "$ca_last" ] || (( ca_last < NOW + ca_min_remain_s )); then
	# Make new CA since the newest one we have is about to expire
	echo "Creating new CA..."
	cert="${CERTDIR}/ca-${ca_new_expire_ts}.crt"
	openssl req -new -newkey rsa:3072 -x509 -days "$ca_days" -extensions v3_ca \
		-nodes -subj "/C=DE/ST=PewPew/L=HeyHey/O=bwLehrpool/CN=ca-${NOW}.bwlehrpool" \
		-keyout "${PRIVDIR}/ca-${ca_new_expire_ts}.key" -out "$cert" || exit 2
	ca_list+=( "$cert" )
	ca_last="${ca_new_expire_ts}"
	latest_ca_cert="$cert"
	if [ -z "$oldest_ca_ts" ]; then
		oldest_ca_ts="$ca_new_expire_ts"
	fi
	mknew=1
fi

# Repackage config.tgz module?
if [ -n "$mknew" ] || ! [ -s "/opt/openslx/configs/modules/self-signed-ca.tar" ] \
		|| ! [ -e "/run/openslx/cert-conf-done" ] \
		|| [ "/opt/openslx/configs/modules/self-signed-ca.tar" -ot "$latest_ca_cert" ]; then
	# Rebuild config module for clients
	echo "Updating client config module..."
	(
		tmpdir="$( mktemp -d '/tmp/bwlp-XXXXXXX' )"
		# Copy all current ca-certs to tmpdir
		cp -a "${CERTDIR}/"ca-*.crt "$tmpdir/"
		cd "$tmpdir/" || exit 6
		# Build hashed symlinks for openssl
		openssl rehash .
		# Put everything in config module and rebuild
		tar -c -k -f "/opt/openslx/configs/modules/self-signed-ca.tar" \
			--transform 's#^[./][./]*#/opt/openslx/ssl/#' .
		cd /tmp || exit 7
		rm -rf -- "$tmpdir"
		sudo -u www-data -n php /srv/openslx/www/slx-admin/api.php sysconfig --action rebuild
		echo "."
		touch "/run/openslx/cert-conf-done"
	)
fi

# Now check the server certificate
# First, check if we could still use old-style certs with intermediate
declare -a unt_list=()
for i in "${CERTDIR}"/intermediate-??????????.crt; do
	[ -s "$i" ] || continue
	if ! get_ts "$i"; then
		echo "Invalid intermediate cert: $i"
		rm -f -- "$i"
		continue
	fi
	if (( ts < NOW )); then
		echo "Expired intermediate: $i"
		rm -f -- "$i"
		continue
	fi
	unt_list+=( "-untrusted" "$i" )
done

# Now check existing server certs
have_srv=
for i in "${PRIVDIR}"/srv-??????????.key; do
	[ -s "$i" ] || continue
	if ! get_ts "$i"; then
		echo "Invalid srv-key: $i"
		rm -f -- "$i"
		continue
	fi
	cert="${CERTDIR}/srv-${ts}.crt"
	if (( ts < NOW + srv_min_remain_s )) || ! [ -s "$cert" ]; then
		echo "Expired srv cert or key with no cert: $i"
		rm -f -- "$i" "$cert"
		continue
	fi
	# Keys match?
	if [ "$( openssl x509 -in "$cert" -noout -pubkey )" != "$( openssl pkey -in "$i" -pubout )" ]; then
		echo "Publickey in cert doesn't match publickey in keypair: $cert + $i"
		rm -f -- "$cert" "$i"
		continue
	fi
	# Validate chain
	valid=
	for ca in "${ca_list[@]}"; do
		if openssl verify -CAfile "$ca" "${unt_list[@]}" \
			"$cert" &> /dev/null; then
			valid=1
			break
		fi
	done
	if [ -n "$valid" ]; then
		have_srv=1
		break
	fi
	echo "No valid CA/chain for $i, removing"
	rm -f -- "$i" "$cert"
done

# Now still check the current lighttpd config, in case it is out of sync
# with our generated stuff for whatever reason.
if [ -n "$have_srv" ] || [ -z "$makenew" ]; then
	if [ -s "${LIGHTDIR}/ca-chain.pem" ]; then
		unt_list=( "-untrusted" "${LIGHTDIR}/ca-chain.pem" )
	else
		unt_list=()
	fi
	valid=
	for ca in "${ca_list[@]}"; do
		openssl verify -CAfile "$ca" "${unt_list[@]}" \
			"${LIGHTDIR}/server.pem" &> /dev/null || continue
		valid=1
		break
	done
	if [ -z "$valid" ]; then
		echo "Current lighttpd SSL setup seems invalid, making new one"
		makenew=1
	fi
fi

# Make new one?
if [ -z "$have_srv" ] || [ -n "$makenew" ]; then
	# Request ServerCert
	csr="$( mktemp /tmp/bwlp-XXXXXXX.csr )"
	echo "Generating new Server Certificate. Key+CSR..."
	rm -f -- "${CERTDIR}"/srv-*.crt "${PRIVDIR}/srv.key.tmp" "${PRIVDIR}"/srv-*.key
	openssl req -newkey rsa:3072 -nodes -keyout "${PRIVDIR}/srv.key.tmp" -out "$csr" \
		-subj "/C=DE/ST=PewPew/L=HeyHey/O=bwLehrpool/CN=satellite.bwlehrpool" || exit 4
	echo "Signing Server Certificate with CA..."
	sign_cert="${CERTDIR}/ca-${oldest_ca_ts}.crt"
	sign_key="${PRIVDIR}/ca-${oldest_ca_ts}.key"
	if ! [ -s "$sign_cert" ] || ! [ -s "$sign_key" ]; then
		echo "CA sign/key to sign does not exist!?"
		maybe_restart
	fi
	echo "Signing with $sign_cert"
	create_conf
	# Need extfile for SAN, chromium doesn't honor CN anymore
	cat > "${csr}.cnf" <<-END
	basicConstraints = CA:FALSE
	nsCertType = server
	nsComment = "bwLehrpool Generated Server Certificate"
	subjectKeyIdentifier = hash
	authorityKeyIdentifier = keyid,issuer:always
	keyUsage = critical, digitalSignature, keyEncipherment
	extendedKeyUsage = serverAuth
	subjectAltName = @alt_names
	[alt_names]
	DNS.1 = satellite.bwlehrpool
	END
	if ! openssl ca -config "$ca_config" -create_serial -policy policy_anything -days "$srv_days" \
			-cert "$sign_cert" -keyfile "$sign_key" -extfile "${csr}.cnf" \
			-notext -name CA_openslx -batch -out "${CERTDIR}/srv-${srv_new_ts}.crt" -in "$csr"; then
		echo "Failed to sign CSR"
		rm -f -- "$sign_key" "$sign_cert"
		maybe_restart
	fi
	rm -rf -- "$ca_dir"
	rm -f -- "$csr" "${csr}.cnf"
	mv "${PRIVDIR}/srv.key.tmp" "${PRIVDIR}/srv-${srv_new_ts}.key" || exit 5

	# Combine and prepare for lighttpd

	mkdir -p "$LIGHTDIR" || exit 10

	# Combine cert and key, as required by (older) lighttpd
	echo "Writing out lighttpd PEMs..."
	cat "${CERTDIR}/srv-${srv_new_ts}.crt" "${PRIVDIR}/srv-${srv_new_ts}.key" > "${LIGHTDIR}/server.pem" || exit 10
	chmod 0600 "${LIGHTDIR}/server.pem"

	# Don't need this anymore
	rm -f -- "${LIGHTDIR}/ca-chain.pem"

	if [ "$1" = "--restart" ] || [ -t 0 ]; then
		echo "Restarting lighttpd..."
		systemctl restart lighttpd.service
	fi
fi

echo "Done."
exit 0