#! /usr/bin/env bash # # Copyright (c) 2014 Roy Keene # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # appfsd_options=() CA_CERT_FILE='AppFS_CA.crt' CA_KEY_FILE='AppFS_CA.key' export CA_CERT_FILE CA_KEY_FILE function call_appfsd() { appfsd "${appfsd_options[@]}" "$@" } function read_password() { local prompt variable prompt="$1" variable="$2" if [ -z "$(eval echo '$'${variable})" ]; then echo -n "${prompt}" >&2 stty -echo IFS='' read -r $variable stty echo echo '' >&2 fi } function read_text() { local prompt variable prompt="$1" variable="$2" if [ -z "$(eval echo '$'${variable})" ]; then echo -n "${prompt}" >&2 IFS='' read -r $variable fi } function generate_ca_cert_and_key() { read_text 'Certificate Authority (CA) Company Name (O): ' CA_DN_S_O read_text 'Certificate Authority (CA) Responsible Party Name (CN): ' CA_DN_S_CN read_password 'Password for Certificate Authority Key: ' CA_PASSWORD export CA_DN_S_O CA_DN_S_CN CA_PASSWORD call_appfsd --tcl ' package require pki set filename_cert $::env(CA_CERT_FILE) set filename_key $::env(CA_KEY_FILE) puts -nonewline "Generating RSA Key..." flush stdout set key [pki::rsa::generate 2048] puts " Done." lappend key subject "O=$::env(CA_DN_S_O),CN=$::env(CA_DN_S_CN)" set ca [pki::x509::create_cert $key $key 1 [clock seconds] [clock add [clock seconds] 5 years] 1 [list] 1] puts "Writing \"$filename_cert\"" set fd [open $filename_cert w 0644] puts $fd $ca close $fd puts "Writing \"$filename_key\"" set fd [open $filename_key w 0400] puts $fd [pki::key $key $::env(CA_PASSWORD)] close $fd ' } function generate_key() { read_password 'Password for Site Key being generated: ' SITE_PASSWORD export SITE_PASSWORD call_appfsd --tcl ' package require pki if {[info exists ::env(SITE_KEY_FILE)]} { set filename_key $::env(SITE_KEY_FILE) } else { set filename_key "AppFS_Site.key" } puts -nonewline "Generating RSA Key..." flush stdout set key [pki::rsa::generate 2048] puts " Done." puts "Writing \"$filename_key\"" set fd [open $filename_key w 0400] puts $fd [pki::key $key $::env(SITE_PASSWORD)] close $fd ' } function generate_csr() { read_text 'Site hostname: ' SITE_HOSTNAME if [ -z "${SITE_KEY_FILE}" ]; then SITE_KEY_FILE="AppFS_Site_${SITE_HOSTNAME}.key" fi export SITE_HOSTNAME SITE_KEY_FILE if [ -f "${SITE_KEY_FILE}" ]; then echo 'Key file already exists.' if cat "${SITE_KEY_FILE}" | grep -i '^Proc-Type: .*,ENCRYPTED' >/dev/null; then read_password 'Password for (existing) Site Key: ' SITE_PASSWORD else SITE_PASSWORD="" fi export SITE_PASSWORD else generate_key fi call_appfsd --tcl ' package require pki if {[info exists ::env(SITE_KEY_FILE)]} { set filename_key $::env(SITE_KEY_FILE) } else { set filename_key "AppFS_Site.key" } set filename_csr "[file rootname $filename_key].csr" set key [read [open $filename_key]] set key [::pki::pkcs::parse_key $key $::env(SITE_PASSWORD)] set csr [::pki::pkcs::create_csr $key [list CN $::env(SITE_HOSTNAME)] 1] puts "Writing \"$filename_csr\"" set fd [open $filename_csr w 0644] puts $fd $csr close $fd ' } function generate_cert() { SITE_CSR_FILE="$1" read_text 'Certificate Signing Request (CSR) file: ' SITE_CSR_FILE if [ -z "${SITE_CSR_FILE}" ]; then generate_csr || exit 1 SITE_CSR_FILE="$(echo "${SITE_KEY_FILE}" | sed 's@.[^\.]*$@@').csr" fi if [ ! -e "${CA_CERT_FILE}" -o ! -e "${CA_KEY_FILE}" ]; then read_text 'Certificate Authority (CA) Certificate Filename: ' CA_CERT_FILE read_text 'Certificate Authority (CA) Key Filename: ' CA_KEY_FILE fi if cat "${CA_KEY_FILE}" | grep -i '^Proc-Type: .*,ENCRYPTED' >/dev/null; then read_password 'Certificate Authority (CA) Password: ' CA_PASSWORD fi SITE_SERIAL_NUMBER="$(uuidgen | dd conv=ucase 2>/dev/null | sed 's@-@@g;s@^@ibase=16; @' | bc -lq)" export SITE_CSR_FILE SITE_SERIAL_NUMBER CA_CERT_FILE CA_KEY_FILE CA_PASSWORD SITE_CERT="$(call_appfsd --tcl ' package require pki set csr [read [open $::env(SITE_CSR_FILE)]] set csr [::pki::pkcs::parse_csr $csr] set ca_key [read [open $::env(CA_KEY_FILE)]] set ca_cert [read [open $::env(CA_CERT_FILE)]] set ca_key [::pki::pkcs::parse_key $ca_key $::env(CA_PASSWORD)] set ca_cert [::pki::x509::parse_cert $ca_cert] set ca_key [concat $ca_key $ca_cert] set cert [::pki::x509::create_cert $csr $ca_key $::env(SITE_SERIAL_NUMBER) [clock seconds] [clock add [clock seconds] 1 year] 0 [list] 1] puts $cert ')" SITE_SUBJECT="$(echo "${SITE_CERT}" | openssl x509 -subject -noout | sed 's@.*= @@')" echo "${USER}@${HOSTNAME} $(date): ${SITE_SERIAL_NUMBER} ${SITE_SUBJECT}" >> "${CA_KEY_FILE}.issued" echo "${SITE_CERT}" | ( if [ -z "${SITE_HOSTNAME}" ]; then cat else tee "AppFS_Site_${SITE_HOSTNAME}.crt" fi ) } function generate_selfsigned() { read_password 'Password for Key: ' SITE_PASSWORD read_text 'Site hostname: ' SITE_HOSTNAME SITE_SERIAL_NUMBER="$(uuidgen | dd conv=ucase 2>/dev/null | sed 's@-@@g;s@^@ibase=16; @' | bc -lq)" export SITE_PASSWORD SITE_HOSTNAME SITE_SERIAL_NUMBER call_appfsd --tcl ' package require pki set filename_cert "AppFS_Site_$::env(SITE_HOSTNAME).crt" set filename_key "AppFS_Site_$::env(SITE_HOSTNAME).key" puts -nonewline "Generating RSA Key..." flush stdout set key [pki::rsa::generate 2048] puts " Done." lappend key subject "CN=$::env(SITE_HOSTNAME)" set cert [pki::x509::create_cert $key $key $::env(SITE_SERIAL_NUMBER) [clock seconds] [clock add [clock seconds] 1 years] 1 [list] 1] puts "Writing \"$filename_cert\"" set fd [open $filename_cert w 0644] puts $fd $cert close $fd puts "Writing \"$filename_key\"" set fd [open $filename_key w 0400] puts $fd [pki::key $key $::env(SITE_PASSWORD)] close $fd ' } function sign_site() { SITE_INDEX_FILE="$1" SITE_KEY_FILE="$2" SITE_CERT_FILE="$3" read_text 'AppFS Site Index file: ' SITE_INDEX_FILE read_text 'Site Key filename: ' SITE_KEY_FILE read_text 'Site Certificate filename: ' SITE_CERT_FILE if cat "${SITE_KEY_FILE}" | grep -i '^Proc-Type: .*,ENCRYPTED' >/dev/null; then read_password "Password for Key (${SITE_KEY_FILE}): " SITE_PASSWORD else SITE_PASSWORD="" fi export SITE_INDEX_FILE SITE_KEY_FILE SITE_CERT_FILE SITE_PASSWORD call_appfsd --tcl "$(cat <<\_EOF_ package require pki set fd [open $::env(SITE_INDEX_FILE)] gets $fd line close $fd set line [split $line ","] # Data to be signed set data [join [lrange $line 0 1] ","] set key [read [open $::env(SITE_KEY_FILE)]] set key [::pki::pkcs::parse_key $key $::env(SITE_PASSWORD)] set cert [read [open $::env(SITE_CERT_FILE)]] array set cert_arr [::pki::_parse_pem $cert "-----BEGIN CERTIFICATE-----" "-----END CERTIFICATE-----"] binary scan $cert_arr(data) H* cert set signature [::pki::sign $data $key] binary scan $signature H* signature set data [split $data ","] lappend data $cert lappend data $signature set data [join $data ","] if {![info exists ::env(APPFS_SIGN_IN_PLACE)]} { set fd [open "$::env(SITE_INDEX_FILE).new" "w" 0644] puts $fd $data close $fd file rename -force -- "$::env(SITE_INDEX_FILE).new" $::env(SITE_INDEX_FILE) } else { set fd [open "$::env(SITE_INDEX_FILE)" "w" 0644] puts $fd $data close $fd } _EOF_ )" } cmd="$1" shift case "${cmd}" in generate-ca) generate_ca_cert_and_key "$@" || exit 1 ;; generate-key) # Hidden, users should use "generate-csr" instead generate_key "$@" || exit 1 ;; generate-csr) generate_csr "$@" || exit 1 ;; sign-csr|generate-cert) generate_cert "$@" || exit 1 ;; generate-selfsigned) generate_selfsigned "$@" || exit 1 ;; sign-site) sign_site "$@" || exit 1 ;; *) echo 'Usage: appfs-cert {generate-selfsigned|generate-ca|generate-csr|sign-csr|generate-cert|sign-site}' >&2 exit 1 ;; esac exit 0