module Pkg::Sign::Msi
  module_function

  def sign(target_dir = 'pkg')
    use_identity = "-i #{Pkg::Config.msi_signing_ssh_key}" if Pkg::Config.msi_signing_ssh_key

    ssh_host_string = "#{use_identity} Administrator@#{Pkg::Config.msi_signing_server}"
    rsync_host_string = "-e 'ssh #{use_identity}' Administrator@#{Pkg::Config.msi_signing_server}"

    work_dir = "Windows/Temp/#{Pkg::Util.rand_string}"
    Pkg::Util::Net.remote_ssh_cmd(ssh_host_string, "mkdir -p C:/#{work_dir}")
    msis = Dir.glob("#{target_dir}/windows*/**/*.msi")
    Pkg::Util::Net.rsync_to(msis.join(" "), rsync_host_string, "/cygdrive/c/#{work_dir}",
                           extra_flags: ["--ignore-existing --relative"])

    # Please Note:
    # We are currently adding two signatures to the msi.
    #
    # Microsoft compatable Signatures are composed of three different
    # elements.
    #   1) The Certificate used to sign the package. This is the element that
    #     is attached to organization. The certificate has an associated
    #     algorithm. We recently (February 2016) had to switch from a sha1 to
    #     a sha256 certificate. Sha1 was deprecated by many Microsoft
    #     elements on 2016-01-01, which forced us to switch to a sha256 cert.
    #     This sha256 certificate is recognized by all currently supported
    #     windows platforms (Windows 8/Vista forward).
    #   2) The signature used to attach the certificate to the package. This
    #     can be a done with a variety of digest algorithms. Older platforms
    #     (i.e., Windows 8 and Windows Vista) don't recognize later
    #     algorithms like sha256.
    #   3) The timestamp used to validate when the package was signed. This
    #     comes from an external source and can be delivered with a variety
    #     of digest algorithms. Older platforms do not recognize newer
    #     algorithms like sha256.
    #
    # We could have only one signature with the Sha256 Cert, Sha1 Signature,
    # and Sha1 Timestamp, but that would be too easy. The sha256 signature
    # and timestamp add more security to our packages. We can't have only
    # sha256 elements in our package signature, though, because Windows 8
    # and Windows Vista just don't recognize them at all.
    #
    # In order to add two signatures to an MSI, we also need to change the
    # tool we use to sign packages with. Previously, we were using SignTool
    # which is the Microsoft blessed program used to sign packages. However,
    # this tool isn't able to add two signatures to an MSI specifically. It
    # can dual-sign an exe, just not an MSI. In order to get the dual-signed
    # packages, we decided to switch over to using osslsigncode. The original
    # project didn't have support to compile on a windows system, so we
    # decided to use this fork. The binaries on the signer were pulled from
    # https://sourceforge.net/u/keeely/osslsigncode/ci/master/tree/
    #
    # These are our signatures:
    # The first signature:
    #   * Sha256 Certificate
    #   * Sha1 Signature
    #   * Sha1 Timestamp
    #
    # The second signature:
    #   * Sha256 Certificate
    #   * Sha256 Signature
    #   * Sha256 Timestamp
    #
    # Once we no longer support Windows 8/Windows Vista, we can remove the
    # first Sha1 signature.
    sign_command = <<-CMD
for msipath in #{msis.join(" ")}; do
  msi="$(basename $msipath)"
  msidir="C:/#{work_dir}/$(dirname $msipath)"
  if "/cygdrive/c/tools/osslsigncode-fork/osslsigncode.exe" verify -in "$msidir/$msi" ; then
    echo "$msi is already signed, skipping . . ." ;
  else
    tries=5
    sha1Servers=(http://timestamp.verisign.com/scripts/timstamp.dll
    http://timestamp.globalsign.com/scripts/timstamp.dll
    http://www.startssl.com/timestamp)
    for timeserver in "${sha1Servers[@]}"; do
      for ((try=1; try<=$tries; try++)) do
        ret=$(/cygdrive/c/tools/osslsigncode-fork/osslsigncode.exe sign \
          -n "Puppet" -i "http://www.puppet.com" \
          -h sha1 \
          -pkcs12 "#{Pkg::Config.msi_signing_cert}" \
          -pass "#{Pkg::Config.msi_signing_cert_pw}" \
          -t "$timeserver" \
          -in "$msidir/$msi" \
          -out "$msidir/signed-$msi")
        if [[ $ret == *"Succeeded"* ]]; then break; fi
      done;
      if [[ $ret == *"Succeeded"* ]]; then break; fi
    done;
    echo $ret
    if [[ $ret != *"Succeeded"* ]]; then exit 1; fi
    sha256Servers=(http://timestamp.digicert.com/sha256/timestamp
      http://timestamp.comodoca.com?td=sha256)
    for timeserver in "${sha256Servers[@]}"; do
      for ((try=1; try<=$tries; try++)) do
        ret=$(/cygdrive/c/tools/osslsigncode-fork/osslsigncode.exe sign \
          -n "Puppet" -i "http://www.puppet.com" \
          -nest -h sha256 \
          -pkcs12 "#{Pkg::Config.msi_signing_cert}" \
          -pass "#{Pkg::Config.msi_signing_cert_pw}" \
          -ts "$timeserver" \
          -in "$msidir/signed-$msi" \
          -out "$msidir/$msi")
        if [[ $ret == *"Succeeded"* ]]; then break; fi
      done;
      if [[ $ret == *"Succeeded"* ]]; then break; fi
    done;
    echo $ret
    if [[ $ret != *"Succeeded"* ]]; then exit 1; fi
  fi
done
CMD

    Pkg::Util::Net.remote_ssh_cmd(ssh_host_string, sign_command, false, '', false)
    msis.each do | msi |
      Pkg::Util::Net.rsync_from("/cygdrive/c/#{work_dir}/#{msi}", rsync_host_string, File.dirname(msi))
    end
    Pkg::Util::Net.remote_ssh_cmd(ssh_host_string, "if [ -d '/cygdrive/c/#{work_dir}' ]; then rm -rf '/cygdrive/c/#{work_dir}'; fi")
  end
end