Debian Verifier

12 minutos de leitura

1. Introdução

Sempre que se recomenda que a assinatura digital e os hashes de um arquivo sejam verificados, o usuário comum esbarra em algumas dificuldades para compreender o processo e executar os comandos necessários. Algumas vezes por falta de conhecimento técnico, outras porque o processo pode variar de acordo com a forma como a assinatura foi gerada ou com a criptografia usada.

O script Debian Verifier faz apenas isso: baixa a imagem, a assinatura digital e os hashes do Debian Linux e verifica se a assinatura é válida e se os hashes são os mesmos. É um script (bash ou python) que não precisa de privilégios de root e roda no macOS e no Linux (a versão python deve rodar também no Windows). Assim como no casos dos pacotes do Decred verificados pelo Decred Verifier, foi gerado um hash SHA-256 de cada arquivo, esses hashes foram inseridos em um arquivo de texto chamado SHA256SUMS, e esse arquivo foi digitalmente assinado.

Para baixar a chave pública que verifica a autenticidade do Debian Linux será iniciada uma conexão TCP 11371 (hkp) para falar com o servidor de chaves do Debian e para baixar a imagem do Debian Linux será necessário falar TCP 443 (https) com o site oficial.

Saiba mais sobre a verificação de assinaturas digitais ou sobre os procedimentos para verificação da assinatura digital do Debian Linux.

2. Bash Script

Para executar o script, copie o código de ~200 linhas para um arquivo local e adicione o atributo de execução como mostrado a seguir:

$ 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

Para executar o script no Python será necessário instalar também os pacotes abaixo. No Windows, será necessário instalar o Python e o gpg4win:

$ 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)