#!/bin/bash

{ set -e

  # save stdin for reading the PKCS#12 password and stdout for writing the PKCS#12 file
  exec 8<&0 0</dev/null 9>&1 1>&2

  # no surprises, please
  export LANG=C LC_ALL=C

  # configuration
  pdfcadir='/www/data/it-portal/PDF-CA'                                        # complete path of PDF-CA directory
  pdfcausr='meinziv'                                                           # user name of certifier
  pdfcagrp='u0infosy'                                                          # group name of certifier
  pdfcanam='/C=DE/O=Universität Münster/CN=Zertifizierungsstelle für interne PDF-Zertifikate'
  pdfcapwd='/www/data/it-portal/secret/pdfca.dat'                              # file containing top secret password of CA key
  pemfile="root@upload.uni-muenster.de:/www/data/WWUCA/htdocs/WWUCA/PDF-CA.pem"
  p7bfile="root@upload.uni-muenster.de:/www/data/WWUCA/htdocs/WWUCA/PDF-CA.p7b"
  crtfile="root@upload.uni-muenster.de:/www/data/WWUCA/htdocs/WWUCA/PDF-CA.crt"
  cerfile="root@upload.uni-muenster.de:/www/data/WWUCA/htdocs/WWUCA/PDF-CA.cer"
  derfile="root@upload.uni-muenster.de:/www/data/WWUCA/htdocs/WWUCA/PDF-CA.der"
  crlfile="root@upload.uni-muenster.de:/www/data/WWUCA/htdocs/WWUCA/PDF-CA.crl"
  pemurl="https://www.uni-muenster.de/WWUCA/PDF-CA.pem"                        # URL of CA certificate
  p7burl="https://www.uni-muenster.de/WWUCA/PDF-CA.p7b"                        # URL of CA certificate
  crlurl="https://www.uni-muenster.de/WWUCA/PDF-CA.crl"                        # URL of revocation list
  scpopts="-p -oControlPath=/www/data/it-portal/run/ssh-meinziv-upload-root"   # scp options
  # end of configuration

  # lock against concurrent running, with 20 minute timeout
  function lockme {
    /usr/bin/find "$pdfcadir/lockdir" -mmin +20 -delete 2>/dev/null || /bin/true
    ((n=0)) || /bin/true
    while ! /bin/mkdir "$pdfcadir/lockdir" 2>/dev/null
    do
      # try 240 times, 4 times per second
      if ((n==240))
      then
        echo "Cannot get exclusive access to Certification Authority" >&2
        exit 1
      fi
      ((n++))
      /bin/sleep .25
    done
    trap '/bin/rmdir "$pdfcadir/lockdir"' EXIT
  }

  case "$1" in

    (--init)
      set -e
      shift
      umask 077

      test "$1" = "Ja, wirklich"

      # save old PDF-CA
      /bin/mv "$pdfcadir" "$pdfcadir.before.$( /usr/bin/date '+%Y-%m-%dT%H:%M:%S%:z' )" 2>/dev/null || /bin/true

      # setup new PDF-CA
      /bin/mkdir -p                    "$pdfcadir"
      /bin/chown "$pdfcausr:$pdfcagrp" "$pdfcadir"
      /bin/chmod 02700                 "$pdfcadir"

      lockme

      cd                               "$pdfcadir"
      /bin/mkdir                       "$pdfcadir/certs" "$pdfcadir/crl" "$pdfcadir/private" "$pdfcadir/dat"
      /bin/chown "$pdfcausr:$pdfcagrp" "$pdfcadir/certs" "$pdfcadir/crl" "$pdfcadir/private" "$pdfcadir/dat"
      /bin/chmod 02700                 "$pdfcadir/certs" "$pdfcadir/crl" "$pdfcadir/private" "$pdfcadir/dat"
      /bin/touch                       "$pdfcadir/openssl.conf" "$pdfcadir/index.txt" "$pdfcadir/index.txt.attr" "$pdfcadir/serial" "$pdfcadir/certs.txt"
      /bin/chown "$pdfcausr:$pdfcagrp" "$pdfcadir/openssl.conf" "$pdfcadir/index.txt" "$pdfcadir/index.txt.attr" "$pdfcadir/serial" "$pdfcadir/certs.txt"
      /bin/chmod 00600                 "$pdfcadir/openssl.conf" "$pdfcadir/index.txt" "$pdfcadir/index.txt.attr" "$pdfcadir/serial" "$pdfcadir/certs.txt"

      # create new configuration file
      /bin/cat <<......EOF | sed 's/^ *//g' >"$pdfcadir/openssl.conf"
      RANDFILE = "$pdfcadir/private/randfile"

      [ ca ]
      default_ca             = PDFCA

      [ req ]
      default_bits           = 8192
      oid_section            = oids
      default_md             = sha256
      distinguished_name     = PDFDN
      extensions             = PDFCAcaext

      [ oids ]
      MicrosoftUPN           = 1.3.6.1.4.1.311.20.2.3
      UID                    = 0.9.2342.19200300.100.1.1

      [ PDFDN ]
      commonName             = CN
      commonName_min         = 1
      commonName_max         = 256
      emailAddress           = emailAddress
      emailAddress_min       = 1
      emailAddress_max       = 128

      [ PDFCA ]
      cert_opt               = ca_default
      certificate            = "$pdfcadir/CA.pem"
      certs_dir              = "$pdfcadir/certs"
      copy_extensions        = none
      database               = "$pdfcadir/index.txt"
      default_days           = 1461
      default_crl_days       = 11688
      default_md             = sha256
      email_in_dn            = yes
      name_opt               = ca_default
      new_certs_dir          = "$pdfcadir/certs"
      oid_section            = oids
      policy                 = PDFCApolicy
      preserve               = no
      private_key            = "$pdfcadir/private/CA.key"
      serial                 = "$pdfcadir/serial"
      unique_subject         = no
      x509_extensions        = PDFCAuserext

      [ PDFCApolicy ]
      commonName             = supplied
      emailAddress           = supplied

      [ PDFCAcaext ]
      subjectKeyIdentifier   = hash
      authorityKeyIdentifier = keyid:always,issuer:always
      basicConstraints       = CA:true
      authorityInfoAccess    = caIssuers;URI:$pemurl
      crlDistributionPoints  = URI:$crlurl

      [ PDFCAuserext ]
      subjectKeyIdentifier   = hash
      authorityKeyIdentifier = keyid,issuer:always
      basicConstraints       = critical,CA:false
      issuerAltName          = issuer:copy
      extendedKeyUsage       = emailProtection
      keyUsage               = digitalSignature,nonRepudiation
      authorityInfoAccess    = caIssuers;URI:$pemurl
      crlDistributionPoints  = URI:$crlurl
      nsCertType             = email
      nsComment              = "Zertifikat nur zum Signieren interner PDF-Dokumente"
......EOF

      # initial fill of random file with real reandom bits
      /bin/dd if=/dev/random of="$pdfcadir/private/randfile" bs=1 count=128

      # create self-signed root certificate valid for 32 years
      /usr/bin/openssl req -new \
        -config "$pdfcadir/openssl.conf" \
        -rand "$pdfcadir/private/randfile" \
        -batch -sha256 -utf8 \
        -newkey rsa:8192 \
        -keyout "$pdfcadir/private/CA.key" -passout fd:4 4<"$pdfcapwd" \
        -x509 -days 11688 -set_serial 1 -extensions PDFCAcaext \
        -subj "$pdfcanam" \
        -out "$pdfcadir/CA.pem"
      /usr/bin/openssl x509 \
        -in "$pdfcadir/CA.pem" -inform pem \
        -out "$pdfcadir/CA.der" -outform der
      /usr/bin/openssl crl2pkcs7 -nocrl \
        -certfile "$pdfcadir/CA.pem" \
        -out "$pdfcadir/CA.p7b" -outform DER

      # initialize counter for serial numbers, just in case
      echo '0000000000000002' >"$pdfcadir/serial"

      # create initial revocation list
      /usr/bin/openssl ca \
        -config "$pdfcadir/openssl.conf" \
        -gencrl -passin fd:3 3<"$pdfcapwd" \
        -out "$pdfcadir/CA.rvk"
      /usr/bin/openssl crl \
        -in "$pdfcadir/CA.rvk" -inform pem \
        -out "$pdfcadir/CA.crl" -outform der

      # upload all public files to web server
      /bin/chmod 00644 "$pdfcadir/CA.pem" "$pdfcadir/CA.p7b" "$pdfcadir/CA.der" "$pdfcadir/CA.rvk" "$pdfcadir/CA.crl"
      /usr/bin/scp $scpopts "$pdfcadir/CA.pem" "$pemfile"
      /usr/bin/scp $scpopts "$pdfcadir/CA.pem" "$crtfile"
      /usr/bin/scp $scpopts "$pdfcadir/CA.p7b" "$p7bfile"
      /usr/bin/scp $scpopts "$pdfcadir/CA.der" "$cerfile"
      /usr/bin/scp $scpopts "$pdfcadir/CA.der" "$derfile"
      /usr/bin/scp $scpopts "$pdfcadir/CA.crl" "$crlfile"
    ;;

    (--cert)
      set -e
      shift
      umask 077
      lockme

      # get and check parameters
      if test -z "$1" || test -z "$2"
      then
        echo "Usage: pdfca mail name < passwordfile"
        exit 1
      fi
      tab=$'\t'
      mail="${1//$tab/ }" ; xmail="${mail//\\/\\\\}" ; xmail="${xmail//\//\\/}" ; # mail with / \ masked
      name="${2//$tab/ }" ; xname="${name//\\/\\\\}" ; xname="${xname//\//\\/}" ; # name with / \ masked
      dn="/CN=$xname/emailAddress=$xmail"

      # get and check PKCS#12 file password
      IFS= read -ru8 userpass || /bin/true
      if test -z "$userpass"
      then
        echo "Usage: pdfca mail name < passwordfile"
        exit 1
      fi

      # prepare working area
      tmp="$pdfcadir/tmp.$$.$( date +%Y%m%d%H%M%S%N ).$RANDOM$RANDOM$RANDOM"
      /bin/rm -rf            "$tmp" 2>/dev/null || /bin/true
      /bin/mkdir             "$tmp"
      /bin/chgrp "$pdfcagid" "$tmp"
      /bin/chmod 02700       "$tmp"

      # prepare adapted configuration file
      /bin/cp "$pdfcadir/openssl.conf" "$tmp/openssl.conf"
      #echo 'subjectAltName = "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:'"$user"'@wwu.de","email:'"$mail"'"' >>"$tmp/openssl.conf"
      echo 'subjectAltName = "email:'"$mail"'"' >>"$tmp/openssl.conf"

      # get random serial number
      while :
      do
        set -e
        serial="$( /bin/dd if=/dev/random bs=1 count=8 2>/dev/null | /usr/bin/xxd -p -u )"
        case "$serial" in
          (0000000000000000|0000000000000001) : ;;
            ([0-7][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F]) break ;;
          ([89A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F]) : ;;
          (*) echo "Cannot create new serial number [$serial]" >&2 ; exit 1 ;;
        esac
      done
      echo "$serial" >"$pdfcadir/serial"

      # generate key pair
      export RANDFILE="$pdfcadir/private/randfile"
      /usr/bin/openssl req -new \
        -config "$tmp/openssl.conf" \
        -rand "$pdfcadir/private/randfile" \
        -batch -sha256 -utf8 \
        -newkey rsa:8192 \
        -keyout "$tmp/this.key" -passout fd:4 4<<<"$userpass" \
        -out "$tmp/this.req" \
        -subj "$dn"

      # certifiy public key
      /usr/bin/openssl ca \
        -config "$tmp/openssl.conf" \
        -batch -utf8 -md sha256 -passin fd:3 3<"$pdfcapwd" \
        -in "$tmp/this.req" \
        -subj "$dn" \
        -out "$tmp/this.pem"

      # extract begin of validity
      von="$(
        /usr/bin/openssl x509 \
          -in "$tmp/this.pem" \
          -noout -startdate |
        /usr/bin/cut -d= -f2
      )"
      bis="$(
        /usr/bin/openssl x509 \
          -in "$tmp/this.pem" \
          -noout -enddate |
        /usr/bin/cut -d= -f2
      )"
      vonstr="$( /usr/bin/date '+%Y-%m-%d %H:%M:%S %:z' -d "$von" )"
      bisstr="$( /usr/bin/date '+%Y-%m-%d %H:%M:%S %:z' -d "$bis" )"
      vonnum="$( /usr/bin/date '+%s' -d "$von" )"
      bisnum="$( /usr/bin/date '+%s' -d "$bis" )"

      # convert all to digital ID (PKCS#12)
      /usr/bin/openssl pkcs12 -export \
        -rand "$pdfcadir/private/randfile" \
        -inkey "$tmp/this.key" -passin fd:3 3<<<"$userpass" \
        -in "$tmp/this.pem" \
        -certfile "$pdfcadir/CA.pem" \
        -out "$tmp/this.p12" -passout fd:4 4<<<"$userpass" \
        -name "$name <$mail> ($vonstr)"

      # store additional index file
      echo "$serial"$'\t'"$vonnum"$'\t'"$bisnum"$'\t'"$mail"$'\t'"$name" >>"$pdfcadir/certs.txt"

      # output digital ID
      /bin/cat "$tmp/this.p12" >&9

      # clean up
      /bin/rm -rf "$tmp" 2>/dev/null || /bin/true
   ;;

   (--list)
      set -e
      shift
      umask 077
      lockme

      # mark expired certificates
      /usr/bin/openssl ca \
        -config "$pdfcadir/openssl.conf" \
        -updatedb -passin fd:3 3<"$pdfcapwd"

      # output all still valid certificates by joining ...
      /usr/bin/join -t$'\t' <(
        # ... the list of still valid serial numbers from index.txt with ...
        /usr/bin/awk -F$'\t' '$1=="V"{print $4}' <"$pdfcadir/index.txt" | /usr/bin/sort -t$'\t'
      ) <(
        # ... the list of certificates from certs.txt ...
        /usr/bin/sort -t$'\t' <"$pdfcadir/certs.txt"
      ) |
      # ... sorted by issuing date
      /usr/bin/sort -t$'\t' -k2n >&9
   ;;

   (--revoke)
      set -e
      shift
      umask 077
      lockme

      # mark expired certificates
      /usr/bin/openssl ca \
        -config "$pdfcadir/openssl.conf" \
        -updatedb -passin fd:3 3<"$pdfcapwd"

      # revoke all given certificates
      for serial
      do set -e
        /usr/bin/openssl ca \
          -config "$pdfcadir/openssl.conf" \
          -revoke "$pdfcadir/certs/$serial.pem" -passin fd:3 3<"$pdfcapwd"
      done

      # create new revocation list
      /usr/bin/openssl ca \
        -config "$pdfcadir/openssl.conf" \
        -gencrl -passin fd:3 3<"$pdfcapwd" \
        -out "$pdfcadir/CA.rvk"
      /usr/bin/openssl crl \
        -in "$pdfcadir/CA.rvk" -inform pem \
        -out "$pdfcadir/CA.crl" -outform der

      # upload revocation list to web server
      /bin/chmod 00644 "$pdfcadir/CA.pem" "$pdfcadir/CA.der" "$pdfcadir/CA.rvk" "$pdfcadir/CA.crl"
      /usr/bin/scp $scpopts "$pdfcadir/CA.crl" "$crlfile"
   ;;

   (*)
     echo >&2 "Usage:"
     echo >&2 "one-time setup:   pdfca --init"
     echo >&2 "list valid certs: pdfca --list"
     echo >&2 "generate new:     pdfca --cert mail name <<<'password'"
     echo >&2 "revoke by serial: pdfca --revoke serial ..."
   ;;

  esac

  exit
}
