# Targets (targets with "*" require the CLIENT variables on the command line): # all Sets up CA # add* Used by 'zip' # zip* Used for making a client cert for a person's own computer # ctarball* Used for making a client cert for a Linux host # revoke* Revoke's a given cert and re-generates the CRL (don't forget to install it on the server) # tarball For installation on the server # install_keys Run on a Linux host after extracting the tarball; specify DEST # (Don't forget to install and modify the config file.) # # The following variables may/must be present on the command line: # CLIENT (if building a client; mandatory) # CLIENT_REQ_ARGS= (if building a client; used to require a passphrase) # SERVER_ID (if building a server; optional) # KEY_DEPT (optional; specifies organizationalUnitName and alters config file name) # CN (optional; specifies commonName) # KEY_DIR (optional) # KEY_CONFIG (optional) # REQ_ARGS (optional; can be used to pass "-subj 'arg'" -- NOTE QUOTES) # CA_ARGS (optional; can be used to pass "-batch") # CA_DAYS (default: 3650 i.e. 10 years) # SERVER_DAYS (default: 3650 i.e. 10 years) # CLIENT_DAYS (default: 1825 i.e. 5 years) # # The following environment variables may/must be present: # KEY_ORG (mandatory) # SERVER_FQDN (mandatory) # KEY_SIZE (optional; do not change after CA is built; edit server/staff.conf) # # TO-DO: # + add 'remove' target; runs "rm -rf keys/$(CLIENT).* clients/.$(CLIENT)_*stamp clients/$(CLIENT)" # + make 'remove' target depnd on 'revoke' target # == Setup == .EXPORT_ALL_VARIABLES: # must be a single word CLIENT = someone # don't use a passphrase CLIENT_REQ_ARGS = -nodes SERVER = server SERVER_ID = $(SERVER) DEST = /tmp/keys KEY_CONFIG = openssl.cnf KEY_DIR = keys # These might be overriden by an environment variable KEY_SIZE ?= 2048 KEY_DEPT ?= CN ?= SERVER_KEY_FILES = $(KEY_DIR)/ca.crt $(KEY_DIR)/$(SERVER_ID).crt $(KEY_DIR)/$(SERVER_ID).key $(KEY_DIR)/banned_certs.crl $(KEY_DIR)/dh$(KEY_SIZE).pem CLIENT_DEPS = clients/$(CLIENT).conf clients/.$(CLIENT)_stamp CA_DAYS ?= 3650 SERVER_DAYS ?= 3650 CLIENT_DAYS ?= 1825 # *** TARGETS *** .PHONY: all # make sure all required files exist all: $(KEY_DIR)/ca.key $(SERVER_KEY_FILES) # == Client == .PHONY: add cc copy_key conf_clean conf_link revoke renew remove # this target generates the following in clients/: # + a TLS mode client config file called someone.conf # + a directory called someone containing: # - a symbolic link called $KEY_ORG.ovpn # (it will be called $KEY_DEPT.ovpn if $KEY_DEPT is set) # - a keys subdirectory containing symlinks to the client & ca certs and # the the client key # Makes sure that if the .ovpn file is re-created, the zip/tarball will be too. add: $(CLIENT_DEPS) clients/$(CLIENT).conf: clients/_template.conf @if [ -z "$$SERVER_FQDN" ] ; then echo ERROR: SERVER_FQDN not present in environment 2>&1; exit 3 ; fi sed -e "s//$$SERVER_FQDN/" -e "s//$(CLIENT)/" $^ > $@ # -- directory containing links to be archived -- # Use stamp files rather than phony targets to avoid always rebuilding clients/.$(CLIENT)_stamp: clients/$(CLIENT) \ clients/.$(CLIENT)_conf-link-stamp clients/.$(CLIENT)_cert-link-stamp touch $@ clients/$(CLIENT): mkdir -p $@ $@/keys clients/.$(CLIENT)_cert-link-stamp: $(KEY_DIR)/$(CLIENT).crt $(KEY_DIR)/$(CLIENT).key $(KEY_DIR)/ca.crt ln -sv --force ../../../$(KEY_DIR)/$(CLIENT).crt ../../../$(KEY_DIR)/$(CLIENT).key clients/$(CLIENT)/keys ln -s --force ../../../$(KEY_DIR)/ca.crt clients/$(CLIENT)/keys touch $@ conf_link: clients/.$(CLIENT)_conf-link-stamp clients/.$(CLIENT)_conf-link-stamp: clients/$(CLIENT).conf @if [ -z "$$KEY_ORG" ] ; then echo WARNING: KEY_ORG not present in environment ; fi ln -s --force ../$(CLIENT).conf "clients/$(CLIENT)/$${KEY_DEPT:-$$KEY_ORG}.ovpn" touch $@ # -- real .key and .crt files -- $(KEY_DIR)/$(CLIENT).crt: $(KEY_DIR)/$(CLIENT).csr $(KEY_DIR)/ca.key | $(KEY_DIR)/ca.crt openssl ca -config $(KEY_CONFIG) -days $(CLIENT_DAYS) $(CA_ARGS) \ -in $(KEY_DIR)/$(CLIENT).csr -out $(KEY_DIR)/$(CLIENT).crt # Generates new .key and .csr files # -nodes isn't specified here because it might be in $(CLIENT_REQ_ARGS) $(KEY_DIR)/$(CLIENT).key $(KEY_DIR)/$(CLIENT).csr: $(KEY_DIR)/ca.key openssl req -config $(KEY_CONFIG) $(REQ_ARGS) $(CLIENT_REQ_ARGS) \ -new -out $(KEY_DIR)/$(CLIENT).csr \ -newkey rsa:$(KEY_SIZE) -keyout $(KEY_DIR)/$(CLIENT).key chmod 0600 $(KEY_DIR)/$(CLIENT).key # -- utilities -- # Copies a key and generates a new .csr file # (This requires the passphrase to be entered, so is little different from # making a new key) copy_key: $(KEY_DIR)/$(SOURCE_KEY) if ! [ -f "$^" ] ; then echo 'ERROR: $$SOURCE_KEY not set' ; exit 1 ; fi cp $(KEY_DIR)/$(SOURCE_KEY) $(KEY_DIR)/$(CLIENT).key openssl req -config $(KEY_CONFIG) $(REQ_ARGS) $(CLIENT_REQ_ARGS) \ -new -out $(KEY_DIR)/$(CLIENT).csr \ -key $(KEY_DIR)/$(CLIENT).key # Hint: use with CLIENT=\* (but be prepared for warnings) conf_clean: rm -f clients/$(CLIENT)/*.ovpn rm -f clients/.$(CLIENT)_conf-link-stamp # -- revocation -- revoke: openssl ca -config $(KEY_CONFIG) -revoke "$(KEY_DIR)/$(CLIENT).crt" $(MAKE) tarball renew: $(KEY_DIR)/$(CLIENT).csr cp -p $(KEY_DIR)/$(CLIENT).crt $(KEY_DIR)/$(CLIENT).crt.old # Revoking old certificate openssl ca -config $(KEY_CONFIG) -revoke "$(KEY_DIR)/$(CLIENT).crt" # Renewing certificate for $(CLIENT_DAYS) days openssl ca -config $(KEY_CONFIG) -days $(CLIENT_DAYS) $(CA_ARGS) \ -in $(KEY_DIR)/$(CLIENT).csr \ -out $(KEY_DIR)/$(CLIENT).crt $(MAKE) zip CLIENT=$(CLIENT) # FIXME remove: rm -f clients/.$(CLIENT)_cert-link-stamp # -- server-side stuff for a client -- # Goes in 'client-config-dir' setting i.e. /etc/openvpn/clients # Requires $VPN_IP_START to be set onespace := $() $() CN := $(shell [ -f "$(KEY_DIR)/$(CLIENT).crt" ] && openssl x509 -subject -noout -in "$(KEY_DIR)/$(CLIENT).crt" | sed 's/.*CN = \([^,]*\).*/\1/') Q_CN := $(subst $(onespace),\ ,$(CN)) cc: clients/$(CLIENT)/$(Q_CN) clients/$(CLIENT)/$(Q_CN): if [ -z "$$VPN_IP_START" ] ; then echo '$$VPN_IP_START not set' ; exit 3 ; fi raw_index=$$(awk '/CN=$(CN)($$|\/)/ { print $$3 }' keys/index.txt) ; \ if [ -n "$$raw_index" ] ; then index=$$(printf '%d\n' 0x$$raw_index) ; echo "ifconfig-push $$VPN_IP_START$$((index * 4 + 2)) $$VPN_IP_START$$((index * 4 + 1))" > "$@" ; else exit 2 ; fi # == Server == .PHONY: server_renew $(KEY_DIR)/dh$(KEY_SIZE).pem: openssl dhparam -out $(KEY_DIR)/dh$(KEY_SIZE).pem $(KEY_SIZE) $(KEY_DIR)/$(SERVER_ID).crt: $(KEY_DIR)/$(SERVER_ID).csr $(KEY_DIR)/ca.key | $(KEY_DIR)/ca.crt $(KEY_DIR)/dh$(KEY_SIZE).pem openssl ca -config $(KEY_CONFIG) $(CA_ARGS) \ -extensions server -days $(SERVER_DAYS) \ -in $(KEY_DIR)/$(SERVER_ID).csr -out $(KEY_DIR)/$(SERVER_ID).crt $(KEY_DIR)/$(SERVER_ID).key $(KEY_DIR)/$(SERVER_ID).csr: $(KEY_DIR)/ca.key CN="$(SERVER_ID)" openssl req -config $(KEY_CONFIG) $(REQ_ARGS) \ -new -extensions server -out $(KEY_DIR)/$(SERVER_ID).csr \ -newkey rsa:$(KEY_SIZE) -keyout $(KEY_DIR)/$(SERVER_ID).key -nodes chmod 0600 $(KEY_DIR)/$(SERVER_ID).key # updates the CRL whenever index.txt has changed # (Avoids having to generate the CRL every time a cert is revoked, # but does take the safe option of making $(SERVER_ID).tar.gz transitively # dependent on $(KEY_DIR)/index.txt .) # (If openssl.cnf's [ crl_ext ] section contains 'authorityKeyIdentifier', this # will cause problems.) $(KEY_DIR)/banned_certs.crl: $(KEY_DIR)/ca.key $(KEY_DIR)/index.txt [ -L $(KEY_DIR)/crl.pem ] || ln -s banned_certs.crl $(KEY_DIR)/crl.pem openssl ca -config $(KEY_CONFIG) \ -gencrl -out $(KEY_DIR)/banned_certs.crl server_renew: $(KEY_DIR)/$(SERVER_ID).csr | $(KEY_DIR)/$(SERVER_ID).crt cp -p $(KEY_DIR)/$(SERVER_ID).crt $(KEY_DIR)/$(SERVER_ID).crt.old # Revoking old certificate openssl ca -config $(KEY_CONFIG) -revoke "$(KEY_DIR)/$(SERVER_ID).crt" # Renewing certificate for $(SERVER_DAYS) days openssl ca -config $(KEY_CONFIG) $(CA_ARGS) \ -extensions server -days $(SERVER_DAYS) \ -in $(KEY_DIR)/$(SERVER_ID).csr -out $(KEY_DIR)/$(SERVER_ID).crt $(MAKE) tarball SERVER=$(SERVER_ID) # == distribution archives == .PHONY: zip tarball ctarball # this target creates a zipfile (no symlinks) for distribution to a client zip: OpenVPN_$(CLIENT).zip OpenVPN_$(CLIENT).zip: $(CLIENT_DEPS) # zip automatically dereferences symlinks (cd clients/$(CLIENT) ; zip -r - . ) > $@ # this target creates a tarball (no symlinks) for distribution to a client machine ctarball: OpenVPN_$(CLIENT).tar.gz OpenVPN_$(CLIENT).tar.gz: $(CLIENT_DEPS) cp -p clients/$(CLIENT).conf "/tmp/$${KEY_DEPT:-$${SERVER_FQDN}}_connect.conf" tar czhvf "$@" -C clients/$(CLIENT) keys -C /tmp "$${KEY_DEPT:-$${SERVER_FQDN}}_connect.conf" chmod 600 "$@" @rm "/tmp/$${KEY_DEPT:-$${SERVER_FQDN}}_connect.conf" tarball: $(SERVER_ID).tar.gz $(SERVER_ID).tar.gz: $(SERVER_ID)/staff.conf $(SERVER_KEY_FILES) tar czf "$@" -C "$(SERVER_ID)" staff.conf scripts/routing_on.up -C .. $(SERVER_KEY_FILES) chmod 600 "$@" # Only copy staff.conf if the target is missing, NOT when updated, hence elide # the source as a dependency # (Should probably use a pristine copy of server/staff.conf) $(SERVER_ID)/staff.conf: mkdir -p $(SERVER_ID)/scripts cp server/staff.conf $@ cp -p server/scripts/routing_on.up $(SERVER_ID)/scripts # == CA == .PHONY: ca_renew # Note: $(REQ_ARGS) is not used $(KEY_DIR)/ca.crt: | $(KEY_DIR) $(KEY_DIR)/ca.key $(KEY_DIR)/ca.key: | $(KEY_DIR) $(KEY_DIR)/index.txt $(KEY_DIR)/serial @echo Creating CA key and certificate openssl req -config $(KEY_CONFIG) \ -new -x509 -days $(CA_DAYS) -out $(KEY_DIR)/ca.crt \ -newkey rsa:$(KEY_SIZE) -keyout $(KEY_DIR)/ca.key -nodes chmod 0600 $(KEY_DIR)/ca.key $(KEY_DIR): mkdir -p $(KEY_DIR) $(KEY_DIR)/index.txt: touch $@ $(KEY_DIR)/serial: echo 01 > $@ # Re-creates $(KEY_DIR)/ca.crt ca_renew: $(KEY_DIR)/ca.csr @echo Renewing CA certificate for $(CA_DAYS) days openssl x509 -req -days $(CA_DAYS) -in $(KEY_DIR)/ca.csr \ -out $(KEY_DIR)/ca.crt.new -signkey $(KEY_DIR)/ca.key mv $(KEY_DIR)/ca.crt $(KEY_DIR)/ca.crt.old mv $(KEY_DIR)/ca.crt.new $(KEY_DIR)/ca.crt # This is needed because the original rule for $(KEY_DIR)/ca.key doesn't make a .csr $(KEY_DIR)/ca.csr: $(KEY_DIR)/ca.crt # Extract a certificate signing request from certification file (PEM) openssl x509 -x509toreq -in $(KEY_DIR)/ca.crt -signkey $(KEY_DIR)/ca.key \ -out $(KEY_DIR)/ca.csr # == Other == .PHONY: install_keys # used to install keys as root for a specific client into a specfic directory # TO-DO: install the config file too install_keys: install -d "$(DEST)" install --mode=644 --preserve-timestamps "$(KEY_DIR)/$(CLIENT).crt" "$(KEY_DIR)/ca.crt" "$(DEST)" install --mode=600 --preserve-timestamps "$(KEY_DIR)/$(CLIENT).key" "$(DEST)"