aboutsummaryrefslogblamecommitdiffstatshomepage
path: root/lets-ca.sh
blob: 36abbca5ee0371397438ccf700ebf70ce12eb06f (plain) (tree)















































                                                                                
                                                                 





























































































































































































































                                                                                

                                                        





































































































                                                                                  
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
#############

# This script creates a key and csr, and uses acme-tiny to have the csr signed
# with lets encrypt.

# by Dennis Eriksen, dennis@eriksen.im, 2015-12-16

#############




################################################################################

# TODO

################################################################################

# Handling of getopts. If you trigger -b after -r, -b doesn't count. Also, you
# don't want to be able to trigger -s and -r, etc.

# There should probably be a file indicating which domains each cert is valid 
# for.. Like $domain/SAN, with an entry for each domain.


################################################################################

# Config 

################################################################################

readonly CERTDIR="/etc/letsencrypt/certs"
readonly CERTDEST="/etc/ssl/letsencrypt"
readonly OPENSSLCONF="/etc/letsencrypt/openssl.cnf"
readonly ACCOUNTKEY="/etc/letsencrypt/account.key"
readonly -a ARGS=("$@")

readonly LETSCAUSER="letsencrypt"
readonly LETSCAGROUP="letsencrypt"

readonly CERTKEYOWNER="root"
readonly CERTGROUP="ssl-cert"



readonly INTERMEDIATE="/etc/ssl/lets-encrypt-x3-cross-signed.pem"


readonly ACMETINY="/usr/local/sbin/acme_tiny.py"

readonly CHALLENGEDIR="/var/www/letsencrypt-challenges"

readonly NGINXCONFSUFFIX=""

BATCHMODE=FALSE

QUIET=FALSE

################################################################################

# Usage

################################################################################

usage() {
  echo "Yes. This is Usage. Hello."
}



################################################################################

# say

################################################################################

echo() {
  if [[ ! "$QUIET" == TRUE ]]; then
    builtin echo $1
  fi
}



################################################################################

# cmdline

################################################################################

cmdline() {

  local bflag=FALSE
  local dflag=FALSE
  local qflag=FALSE
  local rflag=FALSE
  local sflag=FALSE

  while getopts "xqhbr:s:d:" OPTION
  do
    case ${OPTION} in
      b)
        bflag=TRUE
        BATCHMODE=TRUE
        ;;
      h)
        usage
        exit 0
        ;;
      x)
        readonly DEBUG='-x'
        echo "-x was triggered"
        echo "\${ARGS{[@]} = ${ARGS[@]}"
        set -x
        ;;
      s)
        sflag=TRUE
        sign ${OPTARG}
        ;;
      d)
        dflag=TRUE
        deploy ${OPTARG}
        ;;
      q)
        QUIET=TRUE
        ;;
      r)
        rflag=TRUE
        req ${OPTARG} 
        ;;
      \?)
        usage && exit 1
        ;;
      :)
        usage && exit 1
        ;;
    esac
  done
  shift $((OPTIND-1))

  if [[ "$bflag" == TRUE ]] && [[ ! "$rflag" == TRUE ]]; then
    echo "-b is only used with -r."
    echo ""
    usage && exit 1
  fi

  return 0
}


################################################################################

# deploy

################################################################################

deploy() {
  local domain=""

  # Domains are provided separated by spaces.
  IFS=' ' read -r -a domain <<< $1

  if [[ ! ${#domain[@]} == 1 ]]; then
    echo "-d only takes one domain."
    exit 1
  elif [[ ! -d "$CERTDIR/${domain[0]}" ]]; then
    echo "\"${domain[0]}\" does not exist in $CERTDIR"
    exit 1
  elif [[ ! -f "$CERTDIR/${domain[0]}/${domain[0]}.key" ]]; then
    echo "${domain[0]}.key seems to be missing."
    exit 1
  elif [[ ! -f "$CERTDIR/${domain[0]}/${domain[0]}.crt" ]]; then
    echo "${domain[0]}.crt seems to be missing."
    echo "Are you sure you've signed the CSR?"
    exit 1
  fi

  if [[ ! -d "$CERTDEST" ]]; then
    mkdir -p $CERTDEST
    chgrp $CERTGROUP $CERTDEST
  fi


  # Lets's copy!
  echo "Copying ${domain[0]}.crt and ${domain[0]}.key to $CERTDEST"
  cp --preserve=all $CERTDIR/${domain[0]}/${domain[0]}.{crt,key} $CERTDEST

  # If the certificate is meant for nginx, we want to combine the cert and the
  # intermediary certificate.
  if [[ -f "/etc/nginx/sites-enabled/${domain[0]}$NGINXCONFSUFFIX" ]] || \
     [[ -f "$CERTDEST/${domain[0]}.pem" ]]; then
    echo "Detected nginx-config for ${domain[0]}, or existing pem-file."
    echo "Moving crt to pem, and appending intermediate certificate."
    mv $CERTDEST/${domain[0]}.crt $CERTDEST/${domain[0]}.pem
    cat $INTERMEDIATE >> $CERTDEST/${domain[0]}.pem
  fi

  echo ""
  echo "Deployment finished. You should probably reload your configuration."


  exit 0


}


################################################################################

# req

################################################################################

req() {
  local domain=""
  local i=0
  local batch=""

  # Domains are provided separated by spaces.
  IFS=' ' read -r -a domain <<< $1

  # Is batchmode triggered?
  [[ "$BATCHMODE" == TRUE ]] && batch="-batch"

  if [[ -z "$CERTDIR" ]] || [[ -z "${domain[0]}" ]]; then
    echo "\$CERTDIR and/or \${domain[0]} is empty. Something is wrong."
    exit 1
  fi

  if [[ ! -d $CERTDIR ]]; then
    mkdir -p $CERTDIR
    chmod 750 $CERTDIR
    chown $LETSCAUSER:$CERTGROUP $CERTDIR
  fi

  if [[ -d "$CERTDIR/${domain[0]}" ]]; then
    echo "The directory $CERTDIR/${domain[0]} already exists."
    echo "If you continue, it will be deleted."
    echo ""
    read -p "Press Y to continue, or any other button to abort." -n 1 -r

      if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then
        echo "Okay then. Live long and prosper."
        exit 1
      else
        echo ""
        rm -r "$CERTDIR/${domain[0]}" && mkdir -p "$CERTDIR/${domain[0]}"
      fi

  else
    mkdir -p "$CERTDIR/${domain[0]}"
  fi

  # if there is only one domain
  if [[ ${#domain[@]} == 1 ]]; then
    echo "Only one domain - ${domain[0]}"
    openssl req -new -sha256 -nodes -out $CERTDIR/${domain[0]}/${domain[0]}.csr\
      -keyout $CERTDIR/${domain[0]}/${domain[0]}.key -config <(cat $OPENSSLCONF\
      | sed -r "s/REPLACE/${domain[0]}/") $batch

  # if there is more than one domain
  else
    echo "Several domains: "

    SANtext="[SAN]\nsubjectAltName="

    for SAN in "${domain[@]}"; do

      # Echo both to stdout and to SAN-file in cert-dir.
      echo "$SAN" | tee -a "$CERTDIR/${domain[0]}/SAN"

      if [[ ! "$SAN" == "${domain[0]}" ]]; then
        ((i+=1)) && [[ $i > 1 ]] && SANtext+=","
        SANtext+="DNS:$SAN"
      fi
    done
    echo "Common Name will be ${domain[0]}"
    echo $SANtext

    openssl req -new -sha256 -nodes -out $CERTDIR/${domain[0]}/${domain[0]}.csr\
      -keyout $CERTDIR/${domain[0]}/${domain[0]}.key -reqexts SAN -config <(cat\
      $OPENSSLCONF <(printf "$SANtext") | sed -r "s/REPLACE/${domain[0]}/") \
      $batch

  fi

  chown -R $LETSCAUSER:$CERTGROUP $CERTDIR/${domain[0]}
  chmod 750 $CERTDIR/${domain[0]}

  chown $CERTKEYOWNER:$CERTGROUP $CERTDIR/${domain[0]}/${domain[0]}.key
  chmod 440 $CERTDIR/${domain[0]}/${domain[0]}.{csr,key}

  exit 0

}



################################################################################

# sign

################################################################################

sign() {
  local domain=""

  # Domains are provided separated by spaces.
  IFS=' ' read -r -a domain <<< $1

  if [[ ! ${#domain[@]} == 1 ]]; then
    echo "-s only takes one domain."
    exit 1
  elif [[ ! -d "$CERTDIR/${domain[0]}" ]]; then
    echo "\"${domain[0]}\" does not exist in $CERTDIR"
    exit 1
  elif [[ ! -f "$CERTDIR/${domain[0]}/${domain[0]}.key" ]] || \
       [[ ! -f "$CERTDIR/${domain[0]}/${domain[0]}.csr" ]]; then
    echo "The CSR or KEY for ${domain[0]} seems to be missing."
    echo "Are you sure you've created the request?"
    exit 1
  fi

  if [[ ! "$QUIET" == TRUE ]]; then
    echo "Have you remembered to point the address"
    echo ""
    echo "${domain[0]}/.well-known/acme-challenge/"
    echo ""
    echo "to your challenges-directory? - $CHALLENGEDIR"
    echo "See https://github.com/diafygi/acme-tiny#step-3-make-your-website-host"\
         "-challenge-files for more info."
    echo ""
    echo "If the directory isn't available at the above address, "\
         "the signing will fail."
    echo ""

    read -p "Press Y to continue, or any other button to abort." -n 1 -r
    if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then
      echo "Okay then. Live long and prosper."
      exit 1
    fi
  fi

  sudo -u $LETSCAUSER -H python $ACMETINY --account-key $ACCOUNTKEY \
    --csr $CERTDIR/${domain[0]}/${domain[0]}.csr --acme-dir $CHALLENGEDIR \
    > $CERTDIR/${domain[0]}/${domain[0]}.crt

  chown $LETSCAUSER:$CERTGROUP $CERTDIR/${domain[0]}/${domain[0]}.crt 


  exit 0

}



################################################################################

# main

################################################################################

main() {

cmdline ${ARGS[@]}

}



main
exit 0