Debian Verifier

12 minute read

Updated:

1. Introduction

Whenever it is recommended that the digital signature and hashes of a file be verified to avoid files being modified in transit or at rest by trojans or any other malicious code, the average user encounters some difficulties in understanding the process and executing the necessary commands. Sometimes because the user lacks technical knowledge, or because the process may vary depending on how the signature was generated or with the cryptographic method chosen.

The Debian Verifier script does just that: it downloads a Debian Linux image, its digital signatures and hashes from the official website and verifies that the signature is valid and that the hashes match. It is a script (bash or python) that does not need root privileges and runs on macOS and Linux (python version is supposed to run on Windows). Just like with Decred packages verified by Decred Verifier the developers generated a SHA-256 hash of each file, inserted these hashes into a text file named SHA256SUMS, and this file was digitally signed.

To download the public key that verifies the authenticity of Debian Linux the script will initiate a TCP 11371 (hkp) connection to the Debian key server, and to download the image from Debian website it will be necessary to ‘talk’ TCP 443 (https).

Learn more about the verification of digital signatures or about the procedures to verify the digital signature of Debian Linux.

2. Bash Script

To run the script, paste the ~200 lines of code below to a local file and add the executable attribute as shown here:

$ chmod +x debian_verifier.sh

Debian Verifier:

#! /bin/bash -

# Debian Verifier v0.2.1
# This script downloads and verifies the digital signature of Debian Linux (stables releases only).
# Reference: https://www.debian.org/CD/verify
# Author: Marcelo Martins (stakey.club)

# Syntax: debian_verifier.sh -v [version number | "current"] -a $ARCH -m ["cd" | "dvd"]
# -v: Debian version: defaults to 'current' if left blank. Ex: 9.8.0
# -a: Architecture: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x, source or multi-arch
#     $ARCH defaults to 'multi-arch' if left blank
# -m: Media type: 'cd' or 'dvd', defaults to 'cd' if left blank
# Examples: ./debian_verifier.sh
#           ./debian_verifier.sh -v 9.8.0 -a i386 -m dvd
#           ./debian_verifier.sh -v 9.8.0 -a amd64 -m cd

usage() {
	echo "Usage: $0 [-v <string>] [-a <\$ARCH>] [-m <cd|dvd>]" 1>&2
	echo "-v: Debian version: defaults to 'current' if left blank. Ex: 9.8.0"
	echo "-a: \$ARCH may be one of these: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x, multi-arch"
	echo "    \$ARCH defaults to 'multi-arch' if left blank"
	echo "-m: Media type: 'cd' or 'dvd', defaults to 'cd' if left blank"
	exit 1
}

while getopts ":a:v:m:" o; do
    case "${o}" in
        a)
            a=${OPTARG}
            ;;
        v)
            v=${OPTARG}
            ;;
        m)
            m=${OPTARG}
            ;;
        *)
            usage
            ;;
    esac
done
shift $((OPTIND-1))

if [[ -z ${v} ]]; then
    VERSION="current"
elif [[ ${v} =~ ^[0-9]\.[0-9]{1,2}\.?[0-9]{0,2} ]]; then
	VERSION=${v}
else
	echo -e "ERROR: Package version seems to be in the wrong format.\n"
    usage
fi

if [[ -z ${a} || ${a} == "multi-arch" ]]; then
    ARCH_DIR="multi-arch"
    ARCH_FILE="amd64-i386"
elif [[ ${a} =~ [amd64|arm64|armel|armhf|i386|mips|mips64el|mipsel|ppc64el|s390x] ]]; then
    ARCH_DIR=${a}
    ARCH_FILE=${a}
else
    echo -e "ERROR: Architecture is invalid.\n"
    usage
fi

if [[ -z ${m} || ${m} == "cd" ]]; then
    MEDIA_DIR="iso-cd"
    MEDIA_FILE="netinst"
    MEDIA="CD"
elif [[ ${m} == "dvd" ]]; then
    MEDIA_DIR="iso-dvd"
    MEDIA_FILE="DVD-1"
    MEDIA="DVD"
else
    echo -e "ERROR: Media is invalid.\n"
    usage
fi

ARCHIVE_DOWNLOAD="https://cdimage.debian.org/cdimage/archive/$VERSION/$ARCH_DIR/$MEDIA_DIR"
RELEASE_DOWNLOAD="https://cdimage.debian.org/debian-cd/$VERSION/$ARCH_DIR/$MEDIA_DIR"
DEBIAN_DOWNLOAD=""
DEBIAN_MANIFEST="SHA256SUMS"
DEBIAN_MANIFEST_SIGN="SHA256SUMS.sign"
DEBIAN_KEYHOST="keyring.debian.org"
# The keys will be kept hardcoded.
DEBIAN_KEY_FP=(64E6EA7D 6294BE9B 09EA8AC3)
DEBIAN_KEY_ID=()
for k in ${!DEBIAN_KEY_FP[*]}; do
	DEBIAN_KEY_ID=${DEBIAN_KEY_ID[*]}" 0x"${DEBIAN_KEY_FP[k]}
done

[[ ! -x `which gpg` ]] && echo "ERROR: Could not locate gpg." && exit 5
if [[ `uname -a | grep -c "Darwin"` -gt 0 ]]; then
	FLAVOR="Mac"
	[[ ! -x `which curl` ]] && echo "ERROR: Could not locate curl." && exit 6
	LATEST_VERSION=`curl -Ssf -L https://cdimage.debian.org/debian-cd/ | grep '[0-9]\{1,2\}\.[0-9]\{1,2\}.*/' | grep "folder" | grep -v "live" | cut -d'>' -f 3 | cut -d'"' -f 2 | cut -d'/' -f 1`
  	if [[ $VERSION == "current" || $VERSION == $LATEST_VERSION ]]; then
  		VERSION_NUMBER=`curl -s -L $RELEASE_DOWNLOAD | grep -m 1 "debian-[0-9]" | cut -d'>' -f 3 | cut -d'-' -f 2`
  		DEBIAN_DOWNLOAD=$RELEASE_DOWNLOAD
  	else
  		VERSION_NUMBER=$VERSION
  		DEBIAN_DOWNLOAD=$ARCHIVE_DOWNLOAD
  	fi
	if [[ `curl -Is $DEBIAN_DOWNLOAD/ | head -n 1 | grep -c "404"` -gt 0 ]]; then
		echo "ERROR: could not find folder $DEBIAN_DOWNLOAD/."
		if [[ $DEBIAN_DOWNLOAD == $ARCHIVE_DOWNLOAD ]]; then
			echo "The latest version of Debian Linux is $LATEST_VERSION."
			echo "You asked for version $VERSION, for platform $ARCH_DIR in $MEDIA_DIR media."
			echo "There is a chance this media does not exist in the archives anymore."
		fi
		exit 8
	fi
else
	FLAVOR="Linux"
	[[ ! -x `which wget` ]] && echo "ERROR: Could not locate wget." && exit 7
	LATEST_VERSION=`wget -q -O - https://cdimage.debian.org/debian-cd/ | grep '[0-9]\{1,2\}\.[0-9]\{1,2\}.*/' | grep "folder" | grep -v "live" | cut -d'>' -f 3 | cut -d'"' -f 2 | cut -d'/' -f 1`
  	if [[ $VERSION == "current" || $VERSION == $LATEST_VERSION ]]; then
  		VERSION_NUMBER=`wget -q -O - $RELEASE_DOWNLOAD | grep -m 1 "debian-[0-9]" | cut -d'>' -f 3 | cut -d'-' -f 2`
  		DEBIAN_DOWNLOAD=$RELEASE_DOWNLOAD
  	else
  		VERSION_NUMBER=$VERSION
  		DEBIAN_DOWNLOAD=$ARCHIVE_DOWNLOAD
  	fi
	if [[ `wget -S -nv -q -O - $DEBIAN_DOWNLOAD/ | head -n 1 | grep -c "404"` -gt 0 ]]; then
		echo "ERROR: could not find folder $DEBIAN_DOWNLOAD/."
		if [[ $DEBIAN_DOWNLOAD == $ARCHIVE_DOWNLOAD ]]; then
			echo "The latest version of Debian Linux is $LATEST_VERSION."
			echo "You asked for version $VERSION, for platform $ARCH_DIR in $MEDIA_DIR media."
			echo "There is a chance this media does not exist in the archives anymore."
		fi
		exit 8
	fi
fi
DEBIAN_IMAGE="debian-$VERSION_NUMBER-$ARCH_FILE-$MEDIA_FILE.iso"

GREP_STRING="'"
for k in ${!DEBIAN_KEY_FP[*]}; do
	GREP_STRING="$GREP_STRING\|${DEBIAN_KEY_FP[k]}"
done
GREP_STRING="$GREP_STRING\|'"

if [[ `gpg --with-colons -k "Debian" | grep 'fpr' | grep -c "$GREP_STRING"` -eq 3 ]]; then
	echo "Debian signing public keys { ${DEBIAN_KEY_ID[*]} } have already been imported."
else
	# Uses keyring.debian.org to import the key (uses HKP, depends on TCP 11371).
	for i in ${!DEBIAN_KEY_ID[*]}; do
		if [[ `gpg --with-colons -k "Debian" | grep 'fpr' | grep -c ${DEBIAN_KEY_FP[i]}` -eq 0 ]]; then
			echo "Importing Debian signing public key ${DEBIAN_KEY_ID[i]} from $DEBIAN_KEYHOST"
			GPG_OUT=$(gpg --with-colons --status-fd 1 --keyserver $DEBIAN_KEYHOST --recv-keys ${DEBIAN_KEY_ID[i]} 2> /dev/null)
			if [[ `echo $GPG_OUT | grep -c "FAILURE"` -eq 1 ]]; then
				echo "[ FAIL ] GPG failed to import key ${DEBIAN_KEY_ID[i]} from $DEBIAN_KEYHOST. It may have timed out."
				echo "Make sure you can send traffic using port TCP 11371."
				exit 2
			fi
		fi
	done
fi

DOWNLOAD_DIR=${DEBIAN_IMAGE%.*} # Manifest and signature files have the same name for all versions.
[[ ! -d $DOWNLOAD_DIR ]] && mkdir $DOWNLOAD_DIR
if [[ ! -f $DOWNLOAD_DIR/$DEBIAN_IMAGE || ! -f $DOWNLOAD_DIR/$DEBIAN_MANIFEST || ! -f $DOWNLOAD_DIR/$DEBIAN_MANIFEST_SIGN ]]; then
	echo "Downloading files from Debian website to local directory '$DOWNLOAD_DIR'..."
	if [[ $FLAVOR == "Mac" ]]; then
		[[ ! -f $DOWNLOAD_DIR/$DEBIAN_IMAGE ]] && echo "- $DEBIAN_IMAGE" && curl -C - -L -# $DEBIAN_DOWNLOAD/$DEBIAN_IMAGE -o $DOWNLOAD_DIR/$DEBIAN_IMAGE
		[[ ! -f $DOWNLOAD_DIR/$DEBIAN_MANIFEST ]] && echo "- $DEBIAN_MANIFEST" && curl -L -# $DEBIAN_DOWNLOAD/$DEBIAN_MANIFEST -o $DOWNLOAD_DIR/$DEBIAN_MANIFEST
		[[ ! -f $DOWNLOAD_DIR/$DEBIAN_MANIFEST_SIGN ]] && echo "- $DEBIAN_MANIFEST_SIGN" && curl -L -# $DEBIAN_DOWNLOAD/$DEBIAN_MANIFEST_SIGN -o $DOWNLOAD_DIR/$DEBIAN_MANIFEST_SIGN
	else
		[[ ! -f $DOWNLOAD_DIR/$DEBIAN_IMAGE ]] && wget -c -nv -q --show-progress $DEBIAN_DOWNLOAD/$DEBIAN_IMAGE -O $DOWNLOAD_DIR/$DEBIAN_IMAGE
		[[ ! -f $DOWNLOAD_DIR/$DEBIAN_MANIFEST ]] && wget -nv -q --show-progress $DEBIAN_DOWNLOAD/$DEBIAN_MANIFEST -O $DOWNLOAD_DIR/$DEBIAN_MANIFEST
		[[ ! -f $DOWNLOAD_DIR/$DEBIAN_MANIFEST_SIGN ]] && wget -nv -q --show-progress $DEBIAN_DOWNLOAD/$DEBIAN_MANIFEST_SIGN -O $DOWNLOAD_DIR/$DEBIAN_MANIFEST_SIGN
	fi
else
	echo "All files have already been copied to local directory '$DOWNLOAD_DIR'."
	echo "- $DEBIAN_IMAGE"
	echo "- $DEBIAN_MANIFEST"
	echo "- $DEBIAN_MANIFEST_SIGN"
fi

echo -e "\nVerifying digital signature and hashes...\n"
GPG_OUT2=$(gpg --with-colons --status-fd 1 --verify $DOWNLOAD_DIR/$DEBIAN_MANIFEST_SIGN 2> /dev/null)

if [[ `echo $GPG_OUT2 | grep -c "GOOD"` -eq 1 ]]; then
	if [[ $FLAVOR == "Mac" ]]; then
		[[ ! -x `which shasum` ]] && echo "ERROR: Could not locate shasum." && exit 9
		SHASUM=`shasum -a 256 $DOWNLOAD_DIR/$DEBIAN_IMAGE | cut -d' ' -f1`
	else # Linux or ARM
		[[ ! -x `which sha256sum` ]] && echo "ERROR: Could not locate sha256sum." && exit 9
		SHASUM=`sha256sum $DOWNLOAD_DIR/$DEBIAN_IMAGE | cut -d' ' -f1`
	fi

	if [[ `grep -c $SHASUM $DOWNLOAD_DIR/$DEBIAN_MANIFEST` -eq 1 ]]; then
		echo -e "[ SUCCESS ] GPG signature for $DEBIAN_MANIFEST successfully verified."
		echo -e "[ SUCCESS ] SHA-256 hash of $DEBIAN_IMAGE successfully verified.\n"
		# The script won't delete files.
		echo -e "It's now safe to delete files $DEBIAN_MANIFEST and $DEBIAN_MANIFEST_SIGN\n"
		echo "RESULT: SUCCESS. It means that $DEBIAN_IMAGE wasn't modified after its creation"
		echo "and that the file $DEBIAN_MANIFEST that contains the proof was signed by Debian signing private key."
		echo -e "Learn more at https://stakey.club/en/verifying-digital-signatures/\n"
		# The script won't do anything to the downloaded image.
		echo "To use the image:"
		echo "1. burn $DEBIAN_IMAGE to a $MEDIA; or"
		echo "2. use it to boot a virtual machine"
	else
		echo -e "[ SUCCESS ] GPG signature for $DEBIAN_IMAGE successfully verified."
		echo -e "[ FAIL ] SHA-256 hash of $DEBIAN_IMAGE verification FAILED!\n"
		echo "RESULT: FAIL. It means that the manifest wasn't modified after its creation"
		echo "but $DEBIAN_IMAGE is incomplete or was probably modified after its creation."
		exit 3
	fi
else
	echo -e "[ FAIL ] GPG did not find a \"good signature\" for $DEBIAN_IMAGE. Verification FAILED!"
	exit 4
fi

3. Python Script

To execute this Python script, the following packages must be installed on the computer. On Windows, Python and gpg4win must be installed:

$ sudo pip install python-gnupg
$ sudo pip install certifi

$ python debian_verifier.sh

Debian Verifier:

#!/usr/bin/env python3

import argparse
from pathlib import Path
import re
import urllib.request
import ssl
import hashlib
import gnupg
import shutil
import certifi

# Debian Verifier v0.2
# This script downloads and verifies the digital signature of Debian Linux (stables releases only).
# Reference: https://www.debian.org/CD/verify
# Author: Marcelo Martins (stakey.club)

# Syntax: debian_verifier.py -v [version number | "current"] -a $ARCH -m ["cd" | "dvd"]
# -v: Debian version: defaults to 'current' if left blank. Ex: 9.8.0
# -a: Architecture: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x, source or multi-arch
#     $ARCH defaults to 'multi-arch' if left blank
# -m: Media type: 'cd' or 'dvd', defaults to 'cd' if left blank
# Examples: $ python debian_verifier.py
#           $ python debian_verifier.py -v 9.8.0 -a i386 -m dvd
#           $ python debian_verifier.py -v 9.8.0 -a amd64 -m cd

parser = argparse.ArgumentParser()
parser.add_argument("-v", "--version", help="Version to download", dest='version')
parser.add_argument("-a", "--arch", help="Architecture", dest='arch')
parser.add_argument("-m", "--media", help="Media", dest='media')
args = parser.parse_args()

__version__ = '0.2'


def usage():
    print("Usage: $0 [-v <string>] [-a <$ARCH>] [-m <cd|dvd>]")
    print("-v: Debian version: defaults to 'current' if left blank. Ex: 9.8.0")
    print("-a: $ARCH may be one of these: amd64, arm64, armel, armhf, i386, mips, mips64el, "
          "mipsel, ppc64el, s390x, multi-arch")
    print("    $ARCH defaults to 'multi-arch' if left blank")
    print("-m: Media type: 'cd' or 'dvd', defaults to 'cd' if left blank")
    exit(1)


if not args.version:
    VERSION = "current"
elif re.search('^[0-9]{1,2}.[0-9]{1,2}.?[0-9]{0,2}', args.version):
    VERSION = args.version
else:
    VERSION = ""
    print("ERROR: Package version seems to be in the wrong format.\n")
    usage()

if not args.arch:
    ARCH_DIR = "multi-arch"
    ARCH_FILE = "amd64-i386"
elif re.search('amd64|arm64|armel|armhf|i386|mips|mips64el|mipsel|ppc64el|s390x', args.arch):
    ARCH_DIR = args.arch
    ARCH_FILE = args.arch
else:
    ARCH_DIR, ARCH_FILE = "", ""
    print("ERROR: Architecture is invalid.\n")
    usage()

if not args.media or args.media == "cd":
    MEDIA_DIR = "iso-cd"
    MEDIA_FILE = "netinst"
    MEDIA = "CD"
elif args.media == "dvd":
    MEDIA_DIR = "iso-dvd"
    MEDIA_FILE = "DVD-1"
    MEDIA = "DVD"
else:
    MEDIA_DIR, MEDIA_FILE, MEDIA = "", "", ""
    print("ERROR: Media is invalid.\n")
    usage()


HASH_BLOCKSIZE = 65536
ARCHIVE_DOWNLOAD = "https://cdimage.debian.org/cdimage/archive/" + VERSION + "/" + ARCH_DIR + "/" + MEDIA_DIR
RELEASE_DOWNLOAD = "https://cdimage.debian.org/debian-cd/" + VERSION + "/" + ARCH_DIR + "/" + MEDIA_DIR
DEBIAN_DOWNLOAD = ""
DEBIAN_MANIFEST = "SHA256SUMS"
DEBIAN_MANIFEST_SIGN = "SHA256SUMS.sign"
DEBIAN_KEYHOST = "keyring.debian.org"
# The keys will be kept hardcoded.
DEBIAN_KEY_FP = ["64E6EA7D", "6294BE9B", "09EA8AC3"]
DEBIAN_KEY_ID = []
for k in DEBIAN_KEY_FP:
    DEBIAN_KEY_ID.append("0x" + k)

client_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT)
client_context.options |= ssl.OP_NO_TLSv1
client_context.options |= ssl.OP_NO_TLSv1_1
client_context.load_default_certs(purpose=ssl.Purpose.SERVER_AUTH)
client_context.load_verify_locations(capath=certifi.where())

LATEST_VERSION = re.search('([0-9]{1,2}.[0-9]{1,2}.?[0-9]{0,2}?)/',
                           str(urllib.request.urlopen("https://cdimage.debian.org/debian-cd/",
                                                      context=client_context).read(3000))).group(1)

if VERSION == "current" or VERSION == LATEST_VERSION:
    VERSION_NUMBER = re.search('debian-([0-9]{1,2}.[0-9]{1,2}.?[0-9]{0,2})',
                               str(urllib.request.urlopen(RELEASE_DOWNLOAD,
                                                          context=client_context).read(9000))).group(1)
    DEBIAN_DOWNLOAD = RELEASE_DOWNLOAD
else:
    VERSION_NUMBER = VERSION
    DEBIAN_DOWNLOAD = ARCHIVE_DOWNLOAD

response = urllib.request.urlopen(DEBIAN_DOWNLOAD, context=client_context)
if response.code == 404:
    print("ERROR: could not find folder", DEBIAN_DOWNLOAD + "/.")
    if DEBIAN_DOWNLOAD == ARCHIVE_DOWNLOAD:
        print("The latest version of Debian Linux is", LATEST_VERSION + ".")
        print("You asked for version", VERSION + ", for platform", ARCH_DIR, "in", MEDIA_DIR, "media.")
        print("There is a chance this media does not exist in the archives anymore.")
    exit(8)

DEBIAN_IMAGE = "debian-" + VERSION_NUMBER + "-" + ARCH_FILE + "-" + MEDIA_FILE + ".iso"

gpg = gnupg.GPG()
if len(list(set(re.findall("|".join(DEBIAN_KEY_FP), str(gpg.list_keys(keys=DEBIAN_KEY_ID)))))) == 3:
    print("Debian signing public keys", DEBIAN_KEY_ID, "have already been imported.")
else:
    # Uses keyring.debian.org to import the key (uses HKP, depends on TCP 11371).
    for idx, k in enumerate(DEBIAN_KEY_FP):
        if len(list(set(re.findall(str(k), str(gpg.list_keys(keys=DEBIAN_KEY_ID)))))) == 0:
            print("Importing Debian signing public key", DEBIAN_KEY_ID[idx], "from", DEBIAN_KEYHOST)
            gpg_out = gpg.recv_keys(DEBIAN_KEYHOST, DEBIAN_KEY_ID)
            if not gpg_out.results[0]['ok']:
                print("[ FAIL ] GPG failed to import key ${DEBIAN_KEY_ID[i]} from", DEBIAN_KEYHOST,
                      ". It may have timed out.")
                print("Make sure you can send traffic using port TCP 11371.")
                exit(2)

DOWNLOAD_DIR = "debian-" + VERSION_NUMBER + "-" + ARCH_FILE + "-" + MEDIA_FILE
not Path(DOWNLOAD_DIR).is_dir() and Path(DOWNLOAD_DIR).mkdir()
if not Path(DOWNLOAD_DIR + "/" + DEBIAN_IMAGE).is_file() and \
        not Path(DOWNLOAD_DIR + "/" + DEBIAN_MANIFEST).is_file() and \
        not Path(DOWNLOAD_DIR + "/" + DEBIAN_MANIFEST_SIGN).is_file():
    print("Downloading files from Debian website to local directory '" + DOWNLOAD_DIR + "'...")
    if not Path(DOWNLOAD_DIR + "/" + DEBIAN_MANIFEST_SIGN).is_file():
        print("-", DEBIAN_MANIFEST_SIGN)
        with urllib.request.urlopen(DEBIAN_DOWNLOAD + "/" + DEBIAN_MANIFEST_SIGN, context=client_context) as response, \
                open(DOWNLOAD_DIR + "/" + DEBIAN_MANIFEST_SIGN, 'wb') as out_file:
            shutil.copyfileobj(response, out_file)
    if not Path(DOWNLOAD_DIR + "/" + DEBIAN_MANIFEST).is_file():
        print("-", DEBIAN_MANIFEST)
        with urllib.request.urlopen(DEBIAN_DOWNLOAD + "/" + DEBIAN_MANIFEST, context=client_context) as response, \
                open(DOWNLOAD_DIR + "/" + DEBIAN_MANIFEST, 'wb') as out_file:
            shutil.copyfileobj(response, out_file)
    if not Path(DOWNLOAD_DIR + "/" + DEBIAN_IMAGE).is_file():
        print("-", DEBIAN_IMAGE)
        with urllib.request.urlopen(DEBIAN_DOWNLOAD + "/" + DEBIAN_IMAGE, context=client_context) as response, \
                open(DOWNLOAD_DIR + "/" + DEBIAN_IMAGE, 'wb') as out_file:
            shutil.copyfileobj(response, out_file)
else:
    print("All files have already been copied to local directory '" + DOWNLOAD_DIR + "'.")
    print("-", DEBIAN_IMAGE)
    print("-", DEBIAN_MANIFEST)
    print("-", DEBIAN_MANIFEST_SIGN)

print("\nVerifying digital signature and hashes...\n")
v = open(DOWNLOAD_DIR + "/" + DEBIAN_MANIFEST_SIGN, "rb")
verify_result = gpg.verify_file(v, DOWNLOAD_DIR + "/" + DEBIAN_MANIFEST)
v.close()

if verify_result.status == "signature valid":
    hasher = hashlib.sha256()
    with open(DOWNLOAD_DIR + "/" + DEBIAN_IMAGE, 'rb') as hf:
        buf = hf.read(HASH_BLOCKSIZE)
        while len(buf) > 0:
            hasher.update(buf)
            buf = hf.read(HASH_BLOCKSIZE)
    DIGEST = hasher.hexdigest()

    hash_found = False
    file = open(DOWNLOAD_DIR + "/" + DEBIAN_MANIFEST, "r")
    for line in file:
        if re.search(DIGEST, line):
            hash_found = True
    file.close()
    if hash_found:
        print("[ SUCCESS ] GPG signature for", DEBIAN_MANIFEST, "successfully verified.")
        print("[ SUCCESS ] SHA-256 hash of", DEBIAN_IMAGE, "successfully verified.\n")
        # The script won't delete files.
        print("It's now safe to delete files", DEBIAN_MANIFEST, "and", DEBIAN_MANIFEST_SIGN, "\n")
        print("RESULT: SUCCESS. It means that", DEBIAN_IMAGE, "wasn't modified after its creation")
        print("and that the file", DEBIAN_MANIFEST, "that contains the proof was signed by Debian signing private key.")
        print("Learn more at https://stakey.club/en/verifying-digital-signatures/\n")
        # The script won't do anything to the downloaded image.
        print("To use the image:")
        print("1. burn", DEBIAN_IMAGE, "to a", MEDIA + "; or")
        print("2. use it to boot a virtual machine")
    else:
        print("[ SUCCESS ] GPG signature for", DEBIAN_IMAGE, "successfully verified.")
        print("[ FAIL ] SHA-256 hash of", DEBIAN_IMAGE, "verification FAILED!\n")
        print("RESULT: FAIL. It means that the manifest wasn't modified after its creation")
        print("but", DEBIAN_IMAGE, "is incomplete or was probably modified after its creation.")
        exit(3)
else:
    print("[ FAIL ] GPG did not find a \"good signature\" for", DEBIAN_IMAGE + ". Verification FAILED!")
    exit(4)