Letsencrypt & TLSA - automation

John Allen john at klam.ca
Sun Feb 19 19:20:52 CET 2017


Attached is a bash script that I am developing to automate the 
generation of TLSA records from Letsencrypt certificates.

the script is called from the certbot renew hook, it can also be run 
stand alone - Certbot_TLSAgen path-to-certificate "space separated list 
of domains included in cert"

It seems to work, but would some kind sole take a look and where I have 
or are about to screw up.


Any suggestions as to how to get the output into my DNS (Bind9) 
preferably without using nsupdate. I am not keen on nsupdate as it makes 
a mess of the zone files, which I use as documentation for my DNS.


Has anybody heard of a electronic "one time pad" system.

TIA

JohnA





-------------- next part --------------
#!/bin/bash
#=====================================================================================================================
#
#    Copyright (c) 2017 John L. Allen
#
#    This program is free software: you can redistribute it and/or modify it under the terms of the 
#    GNU General Public License as published by the Free Software Foundation, either version 3 of the License,
#    or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
#    without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
#    See the GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#=====================================================================================================================

declare -a TLSA_Services=( smtp imap submission sieve dav davical https )		#	An arrrayof services for which TLSA might be needed

#						these two variables are ????
Certbot_Lineage=									#	path to the certbot generated cert ...(RENEWED_LINEAGE or $1)
declare -a Certbot_Domains 								#	An array into which RENEWED_DOMAINS or $2 Domains are split


#						default values


#TLSA_File_Path="/etc/bind/tlsa/"							#	where do you want to put the TLSA records, default values
TLSA_File_Path="./tlsa_test/"								#	where do you want to put the TLSA records, default values - during testing
TLSA_Filename_Base="tlsa"								#	base filename for the TLSA records - generated  as base_filename.sequence_number
TLSA_TTL=3600										#	TLSA DNS record TTL 
TLSA_Usage=3										#	TLSA usage
TLSA_Selector=1										#	TLSA selector
TLSA_Type=1										#	TLSA type
#
#=====================================================================================================================
#===================Message output function - will need changing to output multi language message=====================
function error_message() { 
    echo $1 1>&2 
    exit 1 
}
#=====================================================================================================================
#================ Check of either the Certbot set environment variables or the command line paramater=================
#================ depending upon how this script is invocked					     =================
#=====================================================================================================================
function Check_Env_Variables() 
{
#	Has command line parameter 1 or the environment variable RENEWED_LINEAGE been set.
#		The Command line parameter overides the environment variable

	[[ -z $RENEWED_LINEAGE ]] &&  Certbot_Lineage=$1 ||  Certbot_Lineage=$RENEWED_LINEAGE
	if [[ -z $Certbot_Lineage ]]; then 
	    error_code= $(error_message "Command line parameter 1 or the evironment variable \"RENEWED_LINEAGE\" was not set, exiting")
	elif ! [[ -d $Certbot_Lineage ]]; then
	    error_code= $(error_message "Command line parameter 1 or the evironment variable \"RENEWED_LINEAGE\" is not a directory, exiting")
	fi

#	Has command line parameter 2 or the environment variable RENEWED_DOMAINS been set.
#		The Command line parameter overides the environment variable
#		If more than one domain is specified they are expected to be in the form of a space seperated list
 
	[[ -z $RENEWED_DOMAINS ]] && Certbot_Domains=( $2 ) || Certbot_Domains=( ${RENEWED_DOMAINS} )
	if [[ ${#Certbot_Domains[@]} -le 0 ]]; then
		error_code=$(error_message "Command line parameter 2 or the evironment variable \"RENEWED_DOMAINS\" was not set, exiting")
	fi

#	Check that the TLSA_File_Path is the location (directory) where files containing the generated TLSA records will be put
#
	if ! [ -d $TLSA_File_Path ]; then
	    error_code=$(error_message "the directory $TLSA_File_Path does not exist, please specify where the generated records are to be stored")
	fi

#	Check that the TLSA filename base has been set.
#		The filename base is used to generate actual filenames in the form of base.sequence
#
	if [ -z "$TLSA_Filename_Base" ]; then
	    error_code=$(error_message "Please specify a base filename into which the generated TLSA records can be placed")
	fi

	return $error_code
}
#=====================================================================================================================
#=======Set the output file name, including path. 
#=======The filename is derived from TLSA_Filename_Base plus a generated serial number suffix
zzz=" "
function Set_Output_Destination() 
{
    count=$( ls -1 $1 | wc -l )
    name=$(printf "%s%s.%04d" "$1" "$2" "$count")
    while [ -a $name ]; do
	count=$(( $count+1))
	name=$(printf "%s%s.%04d" "$1" "$2" "$count")
    done
    echo $name
}

#=====================================================================================================================

#********************************************************************************************************************
#********************************************************************************************************************
#                   THe following functions were stolen from Viktor Dukhovni's tlsagen
#********************************************************************************************************************
#********************************************************************************************************************
#=====================================================================================================================
extract() {
  case "$1" in
  0) openssl x509 -in "$2" -outform DER;;
  1) openssl x509 -in "$2" -noout -pubkey | openssl pkey -pubin -outform DER;;
  esac
}
digest() {
  case "$1" in
  0) cat;;
  1) openssl dgst -sha256 -binary;;
  2) openssl dgst -sha512 -binary;;
  esac
}

Set_TLSA_Usage() {
    case "$(echo $1 | tr '[A-Z]' '[a-z]')" in
	0|pkix-[ct]a)	TLSA_usage=0;;
	1|pkix-ee)	TLSA_usage=1;;
	2|dane-[ct]a)	TLSA_usage=2;;
	3|dane-ee)	TLSA_usage=3;;
	*)		error "Invalid certificate usage: $1";;
    esac
}

Set_TLSA_Selector() {
    case "$(echo $1 | tr '[A-Z]' '[a-z]')" in
	0|cert)		TLSA_selector=0;;
	1|spki|pkey)	TLSA_selector=1;;
	*)		error "Invalid selector: $1";;
    esac
}

Set_TLSA_Type() {
    case "$(echo $1 | tr '[A-Z]' '[a-z]')" in
	0|full) 			TLSA_Type=0;;
	1|sha2-256|sha256|sha-256) 	TLSA_Type=1;;
	2|sha2-512|sha512|sha-512) 	TLSA_Type=2;;
	*)				error "Invalid matching type: $1";;
    esac
}
#=====================================================================================================================

#=====================================================================================================================

#		as we cannot pass and parameters when this is called from the certbot renew hooks
#		we will have to put them somewhere else, 
#		Defailt for general parameters
#

if [ -f /etc/default/Cerbot_TLSAgen.cf ]; then . /etc/default/Cerbot_TLSAgen.cf; fi


Check_Env_Variables "$1" "$2"												# If we are running as a command then these two should be set, if not certbot environment variables should be
[[ $? != 0 ]] && exit													# ooops! exit. this needs some improvement

#******needs testing
#	Certbot_Lineage should now be set, so pick up any cert based paramamters from the ".../live/certname" directory
#
if [ -f ${Certbot_Lineage}Certbot_TLSAgen.cf ]; then . ${Certbot_Lineage}Certbot_TLSAgen.cf; fi

TLSA_Record_Store=$(Set_Output_Destination $TLSA_File_Path $TLSA_Filename_Base)						# generate the output file location Path + generate filename
[[ $? != 0 ]] && exit													# ooops! exit. this needs some improvement

TLSA_Digest=$( extract $TLSA_Selector ${Certbot_Lineage}cert.pem  | digest $TLSA_Type  | od -vAn -tx1 | tr -d ' \012'	# Generate the TLSA key as a HEX string. As we are dealiing with a single cert do this once 
			exit $(( ${PIPESTATUS[0]} | ${PIPESTATUS[1]} | ${PIPESTATUS[2]} )) )
[[ $? != 0 ]] && exit													# ooops! exit. this needs some improvement

#		for each (sub)domain listed, check to see if it is a service doamin that we might like to have a TLSA record for 
#
for domain in $( seq 0 $((${#Certbot_Domains[@]} -1 )) ); do
    TLSA_target=${Certbot_Domains[domain]%.}										# I am not sure whether the Cerbot list of (sub)domains has a trailing "."
    TLSA_domain=${TLSA_target#*.}											# Hopefully this will strip off the sub-domain part giving me a domain-name + TLD
    if [[ -z "${TLSA_domain##*.*}" ]]; then										# Primitive test - if there is a "." this is likely to be domin + TLD
	for service in $( seq 0 $((${#TLSA_Services[@]} -1 )) ); do							# Do this for each of the services that might us a TLSA record
	    while read srv_priority srv_weight srv_port srv_host; 							# read the out put of a DIG for a SRV record 
		do
		    if [ ${srv_host%.} == $TLSA_target ]; then								# if the host returned by dig = the Cerbot target output a TLSA record
			printf "_%d._tcp.%s %d IN TLSA %d %d %d %s\n"\
			       "$srv_port" "$srv_host" "$TLSA_TTL" "$TLSA_Usage" "$TLSA_Selector" "$TLSA_Type"  "$TLSA_Digest" >> $TLSA_Record_Store
		    fi
		done < <(dig SRV _${TLSA_Services[$service]}._tcp.${TLSA_domain} +short)				#
	done
	
#	SOA_domain=$TLSA_domain
#	read soa_ns soa_admin soa_seq soa_t1 soa_t2 soa_t3 soa_t4 < <(dig SOA $SOA_domain +short)
    fi
done


More information about the dane-users mailing list