#!/bin/bash
set -Eeuo pipefail

FORMAT=IBHLINKUA-DEB-SIGNATURE-V1
SIGNATURE_MEMBER=_ibhlinkua
TRUST_DIR="${IBHLINKUA_UPDATE_TRUST_DIR:-/usr/share/ibhlinkua/update-signing}"

fail() {
    echo "Manufacturer signature verification failed: $*" >&2
    exit 1
}

[ "$#" -eq 1 ] || { echo "usage: $0 PACKAGE.deb" >&2; exit 2; }
PACKAGE="$1"
[ -f "${PACKAGE}" ] && [ ! -L "${PACKAGE}" ] || fail "package is not a regular file"
[ -d "${TRUST_DIR}" ] || fail "trusted certificate directory is missing"

WORK_DIR="$(mktemp -d /tmp/ibhlinkua-signature.XXXXXX)"
trap 'rm -rf "${WORK_DIR}"' EXIT

mapfile -t MEMBERS < <(ar t "${PACKAGE}" 2>/dev/null) || fail "cannot read package archive"
[ "${#MEMBERS[@]}" -eq 4 ] || fail "unexpected number of package members"
[ "${MEMBERS[0]}" = debian-binary ] || fail "missing debian-binary member"
[[ "${MEMBERS[1]}" = control.tar.* ]] || fail "missing control archive"
[[ "${MEMBERS[2]}" = data.tar.* ]] || fail "missing data archive"
[ "${MEMBERS[3]}" = "${SIGNATURE_MEMBER}" ] || fail "manufacturer signature is missing"

for index in 0 1 2; do
    ar p "${PACKAGE}" "${MEMBERS[index]}" > "${WORK_DIR}/member-${index}" || fail "cannot extract ${MEMBERS[index]}"
done
ar p "${PACKAGE}" "${SIGNATURE_MEMBER}" > "${WORK_DIR}/signature" || fail "cannot extract signature"

[ "$(wc -l < "${WORK_DIR}/signature")" -eq 5 ] || fail "invalid signature metadata"
for field in format algorithm certificate_sha256 key_id signature; do
    [ "$(grep -c "^${field}=" "${WORK_DIR}/signature")" -eq 1 ] || fail "invalid ${field} field"
done

signature_format="$(sed -n 's/^format=//p' "${WORK_DIR}/signature")"
algorithm="$(sed -n 's/^algorithm=//p' "${WORK_DIR}/signature")"
certificate_sha256="$(sed -n 's/^certificate_sha256=//p' "${WORK_DIR}/signature")"
signature="$(sed -n 's/^signature=//p' "${WORK_DIR}/signature")"
[ "${signature_format}" = "${FORMAT}" ] || fail "unsupported signature format"
[ "${algorithm}" = RS256 ] || fail "unsupported signature algorithm"
[[ "${certificate_sha256}" =~ ^[0-9a-f]{64}$ ]] || fail "invalid certificate fingerprint"
[[ "${signature}" =~ ^[A-Za-z0-9+/]+={0,2}$ ]] || fail "invalid signature encoding"

{
    printf '%s\n' "${FORMAT}"
    for index in 0 1 2; do
        size="$(wc -c < "${WORK_DIR}/member-${index}" | tr -d '[:space:]')"
        digest="$(sha256sum "${WORK_DIR}/member-${index}" | cut -d ' ' -f 1)"
        printf '%s %s %s\n' "${MEMBERS[index]}" "${size}" "${digest}"
    done
} > "${WORK_DIR}/manifest"

trusted_certificate=""
shopt -s nullglob
for certificate in "${TRUST_DIR}"/*.crt; do
    fingerprint="$(openssl x509 -in "${certificate}" -outform DER 2>/dev/null | sha256sum | cut -d ' ' -f 1)" || continue
    if [ "${fingerprint}" = "${certificate_sha256}" ]; then
        trusted_certificate="${certificate}"
        break
    fi
done
[ -n "${trusted_certificate}" ] || fail "signing certificate is not trusted"

printf '%s' "${signature}" | base64 --decode > "${WORK_DIR}/signature.bin" 2>/dev/null || fail "cannot decode signature"
openssl x509 -in "${trusted_certificate}" -pubkey -noout > "${WORK_DIR}/public-key.pem" 2>/dev/null || fail "cannot read trusted certificate"
openssl dgst -sha256 -verify "${WORK_DIR}/public-key.pem" \
    -signature "${WORK_DIR}/signature.bin" "${WORK_DIR}/manifest" >/dev/null 2>&1 || fail "signature does not match package"

echo "Valid IBHsoftec manufacturer signature (${certificate_sha256})"
