#!/bin/bash

# Copyright 2022 NVIDIA Corporation.  All rights reserved.
#
# Script that takes an image file name as an input and does the following:
# Prints out whether SecureBoot is enabled on this hardware.
# Prints out whether the image is signed.
# Prints out whether the signature matches a signer in tbe BIOS, if the
#     image is signed and SecureBoot is enabled.
#
# Exit code:
# 0 = OK to proceed with installation.
# 1 = Image is not signed on hardware with SecureBoot enabled or the
#     signature does not match any signer in the BIOS.
# 2 = Command line error.

image="$1"

if [ "$image" = "" ]; then
    echo "usage: $0 image"
    exit 2
fi

mokutil_sb_state=$( mokutil --sb-state )

if echo $mokutil_sb_state | grep -q "SecureBoot enabled" ; then
    echo "SecureBoot is enabled."
    secureboot=1
elif echo $mokutil_sb_state | grep -q "SecureBoot disabled" ; then
    echo "Warning: SecureBoot is disabled."
    secureboot=0
else
    echo "Warning: SecureBoot is not available."
    secureboot=0
fi

tmpdir=$(mktemp -d /var/tmp/XXXXXXXX )

# Need to split the image into a header and non-header.
# The header is the leading shell script, while the non-header
# is the binary image payload possibly followed by the signature.

sed -e '/exit_marker$/,$d' $image > $tmpdir/header
echo "exit_marker" >> $tmpdir/header
sed -e '1,/exit_marker$/d' $image > $tmpdir/non-header

# If payload_image_size= exists in the header, then it is of the format
# that may be signed, in which case, the non-header will be split into
# the payload and signature.

payload_image_size=$( grep payload_image_size= $tmpdir/header | head -1 | sed -e 's,payload_image_size=,,' )
if [ "$payload_image_size" != "" ]; then
    non_header_size=$( stat -c %s $tmpdir/non-header )
    head -c $payload_image_size $tmpdir/non-header > $tmpdir/payload
    signature_size=$( expr $non_header_size - $payload_image_size )
    tail -c $signature_size $tmpdir/non-header > $tmpdir/signature

    # Verify that the signature format is correct and that the split
    # files can be put back together into the image.
    if ! grep -q -- "-----BEGIN CMS-----" $tmpdir/signature ; then
        echo "Warning: Image $image is not signed (signature has incorrect format)."
        image_is_signed=0
    elif ! cat $tmpdir/header $tmpdir/payload $tmpdir/signature | cmp - $image ; then
        echo "Warning: Image $image is not signed (split files do not match image)."
        image_is_signed=0
    else
        echo "Image is signed."
        image_is_signed=1
    fi
    signature_matches=0
    if [ $secureboot -eq 1 -a $image_is_signed -eq 1 ]; then
        cat $tmpdir/header $tmpdir/payload > $tmpdir/unsigned-image
        pushd $tmpdir > /dev/null
        # Get the signer certificates from the BIOS with mokutil.
        # mokutil puts them in files DB-0001.der, DB-0002.der, ... .
        mokutil --export --db
        openssl_output_file=$( mktemp $tmpdir/XXXXXXXX )
        for auth_sig_der in *.der ; do
            # Convert .der to .pem since the -CAfile needs it to be .pem
            auth_sig_pem=$( echo $auth_sig_der | sed -e 's,.der$,.pem,' )
            openssl x509 -inform der -in $auth_sig_der -outform pem -out $auth_sig_pem
            # Get the common name (CN) to show which signer is being checked.
            cn=$( openssl x509 -in $auth_sig_pem -text | grep "CN =" | head -1 | sed -e 's,.*CN = ,,' )
            echo "Checking signer \"$cn\"."
            echo -n "\"$cn\": " >> $openssl_output_file
            openssl cms -verify -nosmimecap -binary -inform pem -in $tmpdir/signature -content $tmpdir/unsigned-image -CAfile $auth_sig_pem -out /dev/null 2>> $openssl_output_file
            if [ $? -eq 0 ]; then
                signature_matches=1
                break
            fi
        done
        popd > /dev/null
        if [ $signature_matches -eq 1 ]; then
            echo "Signature matches signer \"$cn\"."
        else
            cat $openssl_output_file
            echo "Error: Signature does not match any signer in the BIOS."
        fi
    fi
else
    echo "Warning: Image $image is not signed."
    image_is_signed=0
    signature_matches=0
fi

( cd $tmpdir ; rm -f * )
rmdir $tmpdir

if [ $secureboot -eq 1 -a \( $image_is_signed -eq 0 -o $signature_matches -eq 0 \) ]; then
    echo "Error: With SecureBoot enabled, only a signed image that matches"
    echo "an authorized signer in the BIOS may be installed."
    exit 1
fi

exit 0
