#!/bin/sh
#
# Sign an UEFI capsule file for use with UEFI secure boot. The capsule
# payload is first extracted from the original capsule and then signed
# using UEFI GenerateCapsule tool.
#
# Copyright 2021 Mellanox Technologies. All Rights Reserved.
#
# SPDX-License-Identifier: GPL-2.0 or BSD-3-Clause
#

PROGNAME=$(basename "$0")

usage()
{
    cat <<EOF

Usage:

    $PROGNAME [-e|--engine ENGINE] [-k|--key KEYFILE] [-c|--cert CERTFILE]
              [-d|--digest DIGEST] [-o|--output OUTFILE] INFILE

Sign an UEFI capsule file for use with UEFI secure boot.

arguments:
    -h|--help             print help.
    -e|--engine ENGINE    use engine ENGINE,
                          possibly a hardware device.
    -k|--key KEYFILE      read signing key from KEYFILE.
                          (PEM-encoded private key)
    -c|--cert CERTFILE    read certificate from CERTFILE.
                          (x509 certificate)
    -d|--digest DIGEST    use message digest algorithm DIGEST.
                          (default sha256)
    -o|--output OUTFILE   write signed capsule to OUTFILE.
                          (default INFILE.signed)
    INFILE                read capsule file from INFILE.

EOF
}

engine=
digest=sha256
keyfile=
certfile=
outfile=
infile=

PARSED_OPTIONS=`getopt -a -n "$PROGNAME" -o he:d:k:c:o: \
                    --long help,engine:,digest:,key:,cert:,output: -- "$@"`
eval set -- "$PARSED_OPTIONS"

while true
do
  case $1 in
    -h|--help)
        usage
        exit 0
        ;;
    -e|--engine)
        engine=$2
        shift 2
        ;;
    -d|--digest)
        digest=$2
        shift 2
        ;;
    -k|--key)
        keyfile=$2
        shift 2
        ;;
    -c|--cert)
        certfile=$2
        shift 2
        ;;
    -o|--output)
        outfile=$2
        shift 2
        ;;
    --)
        shift
        break
        ;;
    *)
        echo "** error: unexpected option \"$1\""
        usage
        exit 1
        ;;
  esac
done

if [ ! -n "$keyfile" ]; then
    echo "** error: missing argument -k|--key KEYFILE"
    usage
    exit 1
fi

if [ ! -n "$certfile" ]; then
    echo "** error: missing argument -c|--cert CERTFILE"
    usage
    exit 1
fi

if [ $# -ne 1 ] ; then
    echo "** error: missing argument INFILE"
    usage
    exit 1
fi

infile=$1

#
# Verify script arguments
#

if [ ! -f "$infile" ]; then
    echo "** error: bad input file $infile"
    exit 1
fi

if [ ! -f "$keyfile" ]; then
    echo "** error: bad key file $keyfile"
    exit 1
fi

if [ ! -f "$certfile" ]; then
    echo "** error: bad key file $certfile"
    exit 1
fi

if [ -z "$outfile" ]; then
    outfile=${infile}.signed
fi

#
# Verify script dependencies
#

root=$(dirname "$(readlink -f "$0")")

# Setup PYTHONPATH variable to list the directories that Python
# should add to the sys.path directory list.
export PYTHONPATH="$root"

if [ ! -f "$root/Capsule/GenerateCapsule.py" ]; then
    echo "** error: cannot find \"$root/Capsule/GenerateCapsule.py\""
    exit 1
fi

generate_capsule_py=$root/Capsule/GenerateCapsule.py

#
# Process script arguments
#

tmpfile=${infile}.tmp

# The Python command must produce a JSON file and payload BIN file(s).
# Note that the capsule file may contain a single or multiple payloads.
jsonfile=${tmpfile}.json
payloadfile=${tmpfile}.Payload.*.bin

rm -f $jsonfile $payloadfile

# Extract the payload of the UEFI capsule.
# The decode command may result in warning and openssl error messages,
# such as:
#
#   Error reading S/MIME message
#   139675311490976:error:0D07207B:asn1 encoding routines:ASN1_get_object:header too long:asn1_lib.c:157:
#
#   GenerateCapsule: warning: payload verification failed Index = 1
#   GenerateCapsule: error: openssl failed.
#
# Those errors/warnings can be ignored; and are redirected to /dev/null.
python $generate_capsule_py -d \
            --signer-private-key $keyfile \
            --signer-public-cert $certfile \
            --output $tmpfile \
            $infile \
            >/dev/null 2>&1

if [ ! -s "$jsonfile" ] || \
        [ ! "$(ls $payloadfile)" > /dev/null 2>&1 ]; then
    echo "** error: failed to decode capsule"
    exit 1
fi

encode_extra_params="--digest $digest"
if [ -n "$engine" ]; then
    encode_extra_params="$encode_extra_params --engine $engine"
fi

# Re-generate the capsule file and sign its payload.
# The encode command may result in warning message, such as:
#
#   GenerateCapsule: warning "EmbeddedDrivers" section not found in JSON file ${tmpfile}.json
#
# This warning message can be ignored; and is redirected to /dev/null.
python $generate_capsule_py -e \
            $encode_extra_params \
            --json-file $jsonfile \
            --output $outfile \
            >/dev/null 2>&1

rm -f $tmpfile $jsonfile $payloadfile

if [ ! -s $outfile ]; then
    echo "** error: failed to generate signed capsule"
    exit 1
fi

echo "\"$infile\" signed successfully."
echo "  Signed capsule written to \"$outfile\""

exit 0
