Decred Verifier

11 minutos de leitura

1. Introdução

Sempre que se recomenda que a assinatura digital e os hashes de um arquivo sejam verificados para evitar arquivos modificados no meio do caminho por algum trojan ou outro tipo de código malicioso, 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 Decred Verifier faz apenas isso: baixa os pacotes, a assinatura digital e os hashes dos pacotes do Decred disponíveis para download no repositório oficial no Github e verifica se a assinatura e os hashes são válidos. É 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).

Para baixar a chave dos desenvolvedores do Decred será iniciada uma conexão TCP 11371 (hkp) para falar com o servidor de chaves do MIT e para baixar os pacotes será necessário falar TCP 443 (https) com o Github.

Saiba mais sobre a verificação de assinaturas digitais.

2. Bash Script

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

$ chmod +x decred_verifier.sh

Decred Verifier:

#! /bin/bash -

# Decred Verifier v0.2.1
# This script downloads and verifies the digital signature and hash of Decred packages.
# Decrediton and the Decred installer are developed by Decred developers and are available at decred.org
# Reference: https://github.com/decred/decred-binaries/
# Author: Marcelo Martins (stakey.club)

# Syntax: decred_verifier.sh [version number] [mode]
# Version defaults to the latest auto-detected version if left blank
# Mode of operation must be:
# 0 = Decrediton (default)
# 1 = Decred installer
# Examples: ./decred_verifier.sh
#           ./decred_verifier.sh 1.4.0
#           ./decred_verifier.sh 1.4.0 1

print_help () {
    echo "Version defaults to the latest auto-detected version if left blank"
    echo "Mode of operation must be:"
    echo "0 = Decrediton (default)"
    echo "1 = Decred installer"
    echo "Syntax:   $0 [version] [mode]"
    echo "Examples: $0"
    echo "          $0 1.4.0"
    echo "          $0 1.4.0 0"
    exit 1
}

if [[ `uname -a | grep -c "Darwin"` -gt 0 ]]; then
	FLAVOR="Mac"
elif [[ `uname -a | grep -c "Linux"` -gt 0 ]]; then
	FLAVOR="Linux"
elif [[ `uname -m | grep -c "arm"` -gt 0 ]]; then
	FLAVOR="ARM"
else
    FLAVOR="Unknown"
fi

if [[ -z $1 ]]; then
    if [[ $FLAVOR == "Mac" ]]; then
        VERSION=`curl -Ssf -L https://github.com/decred/decred-binaries/tags | grep -o 'v[0-9]\.[0-9]\{1,2\}\.[0-9]\{1,2\}' | head -n 1 | cut -d'v' -f 2`
    else
        VERSION=`wget -S -nv -q -O - https://github.com/decred/decred-binaries/tags | grep -o 'v[0-9]\.[0-9]\{1,2\}\.[0-9]\{1,2\}' | head -n 1 | cut -d'v' -f 2`
    fi
    if [[ -z $VERSION ]]; then
        echo -e "ERROR: Package version must be provided.\n"
        print_help
    fi
elif [[ $1 =~ ^[0-9]\.[0-9]{1,2}\.?[0-9]{0,2} ]]; then
    VERSION=$1
else
    echo -e "ERROR: Package version seems to be in the wrong format.\n"
    print_help
fi

if [[ -z $2 ]]; then
    MODE=0
elif [[ $2 =~ ^[0-1]$ ]]; then
    MODE=$2
else
    echo -e "ERROR: Mode of operation is invalid.\n"
    print_help
fi

DCR_KEYHOST="pgp.mit.edu"
DCR_KEY="0x518A031D"
DCR_GITHUB="https://github.com/decred/decred-binaries/releases/download"
DCR_GITHUB_TAG="https://github.com/decred/decred-binaries/releases/tag"
MANIFEST_DECREDITON="manifest-decrediton-v$VERSION.txt"
MANIFEST_ASC_DECREDITON="manifest-decrediton-v$VERSION.txt.asc"
DECREDITON_DMG="decrediton-v$VERSION.dmg"
DECREDITON_TAR="decrediton-v$VERSION.tar.gz"
MANIFEST_DECRED="manifest-v$VERSION.txt"
MANIFEST_ASC_DECRED="manifest-v$VERSION.txt.asc"
DECRED_DARWIN="decred-darwin-amd64-v$VERSION.tar.gz"
DECRED_PKG_AMD64="decred-linux-amd64-v$VERSION.tar.gz"
DECRED_PKG_386="decred-linux-386-v$VERSION.tar.gz"
DECRED_PKG_ARM="decred-linux-arm-v$VERSION.tar.gz"
DECRED_PKG_ARM64="decred-linux-arm64-v$VERSION.tar.gz"

[[ ! -x `which gpg` ]] && echo "ERROR: Could not locate gpg." && exit 5
if [[ $FLAVOR == "Mac" ]]; then
	[[ ! -x `which curl` ]] && echo "ERROR: Could not locate curl." && exit 6
	if [[ $MODE -eq 0 ]]; then
		MANIFEST=$MANIFEST_DECREDITON
		MANIFEST_ASC=$MANIFEST_ASC_DECREDITON
		DECRED_PKG=$DECREDITON_DMG
	else
		MANIFEST=$MANIFEST_DECRED
		MANIFEST_ASC=$MANIFEST_ASC_DECRED
		DECRED_PKG=$DECRED_DARWIN
	fi
elif [[ $FLAVOR == "Linux" ]]; then
	[[ ! -x `which wget` ]] && echo "ERROR: Could not locate wget." && exit 7
	if [[ $MODE -eq 0 ]]; then
		MANIFEST=$MANIFEST_DECREDITON
		MANIFEST_ASC=$MANIFEST_ASC_DECREDITON
		DECRED_PKG=$DECREDITON_TAR
	else
		MANIFEST=$MANIFEST_DECRED
		MANIFEST_ASC=$MANIFEST_ASC_DECRED
		if [[ `uname -a | grep -c "386"` -eq 0 ]]; then
			DECRED_PKG=$DECRED_PKG_AMD64
		else
			DECRED_PKG=$DECRED_PKG_386
		fi
	fi
else
	[[ ! -x `which wget` ]] && echo "ERROR: Could not locate wget." && exit 7
	if [[ $MODE -eq 0 ]]; then
		echo "ERROR: There is no Decrediton for ARM platform."
		exit 8
	else
		MANIFEST=$MANIFEST_DECRED
		MANIFEST_ASC=$MANIFEST_ASC_DECRED
		if [[ `uname -a | grep -c "arm64"` -gt 0 ]]; then
			DECRED_PKG=$DECRED_PKG_ARM64
		else
			DECRED_PKG=$DECRED_PKG_ARM
		fi
	fi
fi

if [[ `gpg --with-colons -k $DCR_KEY | grep -c "Decred"` -eq 1 ]]; then
	echo "Decred developers' public key $DCR_KEY has already been imported."
else
	echo "Importing Decred developers' key $DCR_KEY from $DCR_KEYHOST"
	GPG_OUT=$(gpg --with-colons --status-fd 1 --keyserver $DCR_KEYHOST --recv-keys $DCR_KEY 2> /dev/null)
	if [[ `echo $GPG_OUT | grep -c FAILURE` -eq 1 ]]; then
		echo "[ FAIL ] GPG failed to import key $DCR_KEY from $DCR_KEYHOST. It may have timed out."
		echo "Make sure you can send traffic using port TCP 11371."
		exit 2
	fi
fi

if [[ $FLAVOR == "Mac" ]]; then
  if [[ `curl -Is $DCR_GITHUB_TAG/v$VERSION | head -n 1 | grep -c "404"` -gt 0 ]]; then
    echo "ERROR: could not find release version $VERSION on Github."
    exit 8
  fi
else
  if [[ `wget -S -nv -q -O - $DCR_GITHUB_TAG/v$VERSION 2>&1 | head -n 1 | grep -c "404"` -gt 0 ]]; then
    echo "ERROR: could not find release version $VERSION on Github."
    exit 8
  fi
fi

if [[ ! -f $MANIFEST || ! -f $MANIFEST_ASC || ! -f $DECRED_PKG ]]; then
	echo "Downloading files from Github..."
	if [[ $FLAVOR == "Mac" ]]; then
		[[ ! -f $MANIFEST ]] && echo "- $MANIFEST" && curl -L -# $DCR_GITHUB/v$VERSION/$MANIFEST -o $MANIFEST
		[[ ! -f $MANIFEST_ASC ]] && echo "- $MANIFEST_ASC" && curl -L -# $DCR_GITHUB/v$VERSION/$MANIFEST_ASC -o $MANIFEST_ASC
		[[ ! -f $DECRED_PKG ]] && echo "- $DECRED_PKG" && curl -L -# $DCR_GITHUB/v$VERSION/$DECRED_PKG -o $DECRED_PKG
	else
		[[ ! -f $MANIFEST ]] && wget -nv -q --show-progress $DCR_GITHUB/v$VERSION/$MANIFEST
		[[ ! -f $MANIFEST_ASC ]] && wget -nv -q --show-progress $DCR_GITHUB/v$VERSION/$MANIFEST_ASC
		[[ ! -f $DECRED_PKG ]] && wget -nv -q --show-progress $DCR_GITHUB/v$VERSION/$DECRED_PKG
	fi
else
	echo "All files have already been copied locally."
	echo "- $MANIFEST"
	echo "- $MANIFEST_ASC"
	echo "- $DECRED_PKG"
fi

echo -e "\nVerifying digital signature and hash...\n"
GPG_OUT2=$(gpg --with-colons --status-fd 1 --verify $MANIFEST_ASC 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 $DECRED_PKG | cut -d' ' -f1`
	else # Linux or ARM
		[[ ! -x `which sha256sum` ]] && echo "ERROR: Could not locate sha256sum." && exit 9
		SHASUM=`sha256sum $DECRED_PKG | cut -d' ' -f1`
	fi

	if [[ `grep -c $SHASUM $MANIFEST` -eq 1 ]]; then
		echo "[ SUCCESS ] GPG signature for $MANIFEST successfully verified."
		echo -e "[ SUCCESS ] SHA-256 hash of $DECRED_PKG successfully verified.\n"
		echo -e "It's now safe to delete files $MANIFEST and $MANIFEST_ASC\n" # The script won't delete files.
		echo "RESULT: SUCCESS. It means that $DECRED_PKG wasn't modified after its creation"
		echo "and that the file that contains the proof was signed by Decred developers' private key."
		echo -e "Learn more at https://stakey.club/en/verifying-digital-signatures/\n"
		# The script won't install or run the wallet.
		if [[ $FLAVOR == "Mac" && $MODE -eq 0 ]]; then
			echo "To install, open $DECRED_PKG and move Decrediton.app to the Applications folder."
		else
			echo "To install, unpack $DECRED_PKG, enter the directory and run the executable."
		fi
	else
		echo -e "[ SUCCESS ] GPG signature for $DECRED_PKG successfully verified."
		echo -e "[ FAIL ] SHA-256 hash of $DECRED_PKG verification FAILED!\n"
		echo "RESULT: FAIL. It means that the manifest wasn't modified after its creation"
		echo "but $DECRED_PKG is incomplete or was probably modified after its creation."
		exit 3
	fi
else
	echo -e "[ FAIL ] GPG did not find a \"good signature\" for $MANIFEST. 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 decred_verifier.sh

Decred Verifier:

#!/usr/bin/env python3

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

# Decred Verifier v0.2
# This script downloads and verifies the digital signature and hash of Decred packages.
# Decrediton and the Decred installer are developed by Decred developers and are available at decred.org
# Reference: https://github.com/decred/decred-binaries/
# Author: Marcelo Martins (stakey.club)

# Syntax: python decred_verifier.py -v [version number] -m [mode]
# Version defaults to the latest auto-detected version if left blank
# Mode of operation must be:
# 0 = Decrediton (default)
# 1 = Decred installer
# Examples: $ python decred_verifier.py
#           $ python decred_verifier.py -v 1.4.0
#           $ python decred_verifier.py -v 1.4.0 -m 0
#           $ python decred_verifier.py -v 1.4.0 -m 1

parser = argparse.ArgumentParser()
parser.add_argument("-v", "--version", help="Version to download", dest='version')
parser.add_argument("-m", "--mode", help="Mode of operation", dest='mode')
args = parser.parse_args()

__version__ = '0.2'


def print_help():
    print("Version defaults to the latest auto-detected version if left blank")
    print("Mode of operation must be:")
    print("0 = Decrediton (default)")
    print("1 = Decred installer")
    print("Syntax:  $0 -v [version] -m [mode]")
    print("Example: $0")
    print("         $0 -v 1.4.0")
    print("         $0 -v 1.4.0 -m 0")
    exit(1)


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

if not args.version:
    reVERSION = re.search('archive/v([0-9]{1,2}.[0-9]{1,2}.?[0-9]{0,2}).',
                          str(urllib.request.urlopen("https://github.com/decred/decred-binaries/tags",
                                                     context=client_context).read(90000)))
    VERSION = reVERSION.group(1)
    if not VERSION:
        print("ERROR: Package version must be provided.\n")
        print_help()
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")
    print_help()


if not args.mode:
    MODE = 0
elif re.search('^[0-1]$', args.mode):
    MODE = args.mode
else:
    MODE = 0
    print("ERROR: Mode of operation is invalid.\n")
    print_help()

HASH_BLOCKSIZE = 65536
DCR_KEYHOST = "pgp.mit.edu"
DCR_KEY = "0x518A031D"
DCR_KEY_LONG_FP = "6D897EDF518A031D"
GITHUB_HOST = "github.com"
DCR_GITHUB_DIR = "/decred/decred-binaries/releases/download"
DCR_GITHUB = "https://github.com/decred/decred-binaries/releases/download"
DCR_GITHUB_TAG = "https://github.com/decred/decred-binaries/releases/tag"
DCR_GITHUB_TAG_DIR = "/decred/decred-binaries/releases/tag"
MANIFEST_DECREDITON = "manifest-decrediton-v" + VERSION + ".txt"
MANIFEST_ASC_DECREDITON = "manifest-decrediton-v" + VERSION + ".txt.asc"
DECREDITON_DMG = "decrediton-v" + VERSION + ".dmg"
DECREDITON_TAR = "decrediton-v" + VERSION + ".tar.gz"
MANIFEST_DECRED = "manifest-v" + VERSION + ".txt"
MANIFEST_ASC_DECRED = "manifest-v" + VERSION + ".txt.asc"
DECRED_DARWIN = "decred-darwin-amd64-v" + VERSION + ".tar.gz"
DECRED_PKG_AMD64 = "decred-linux-amd64-v" + VERSION + ".tar.gz"
DECRED_PKG_386 = "decred-linux-386-v" + VERSION + ".tar.gz"
DECRED_PKG_ARM = "decred-linux-arm-v" + VERSION + ".tar.gz"
DECRED_PKG_ARM64 = "decred-linux-arm64-v" + VERSION + ".tar.gz"
DECRED_EXE_386 = "decred-windows-386-v" + VERSION + ".zip"
DECRED_EXE_AMD64 = "decred-windows-amd64-v" + VERSION + ".zip"
DECREDITON_EXE = "decrediton-v" + VERSION + ".exe"
MANIFEST, MANIFEST_ASC, DECRED_PKG = "", "", ""

if platform.system() == 'Darwin':
    if int(MODE) == 0:
        MANIFEST = MANIFEST_DECREDITON
        MANIFEST_ASC = MANIFEST_ASC_DECREDITON
        DECRED_PKG = DECREDITON_DMG
    else:
        MANIFEST = MANIFEST_DECRED
        MANIFEST_ASC = MANIFEST_ASC_DECRED
        DECRED_PKG = DECRED_DARWIN
elif platform.system() == 'Linux':
    if os.uname()[4].startswith("arm"):
        if int(MODE) == 0:
            print("ERROR: There is no Decrediton for ARM platform.")
            exit(8)
        else:
            MANIFEST = MANIFEST_DECRED
            MANIFEST_ASC = MANIFEST_ASC_DECRED
            if platform.architecture()[0] == '64bit':
                DECRED_PKG = DECRED_PKG_ARM64
            else:
                DECRED_PKG = DECRED_PKG_ARM
    else:
        if int(MODE) == 0:
            MANIFEST = MANIFEST_DECREDITON
            MANIFEST_ASC = MANIFEST_ASC_DECREDITON
            DECRED_PKG = DECREDITON_TAR
        else:
            MANIFEST = MANIFEST_DECRED
            MANIFEST_ASC = MANIFEST_ASC_DECRED
            if platform.architecture()[0] == '64bit':
                DECRED_PKG = DECRED_PKG_AMD64
            else:
                DECRED_PKG = DECRED_PKG_386
elif platform.system() == 'Windows':
    if int(MODE) == 0:
        MANIFEST = MANIFEST_DECREDITON
        MANIFEST_ASC = MANIFEST_ASC_DECREDITON
        DECRED_PKG = DECREDITON_EXE
    else:
        MANIFEST = MANIFEST_DECRED
        MANIFEST_ASC = MANIFEST_ASC_DECRED
        if platform.architecture()[0] == '64bit':
            DECRED_PKG = DECRED_EXE_AMD64
        else:
            DECRED_PKG = DECRED_EXE_386
else:
    print("ERROR: Unknown platform.")
    exit(9)

gpg = gnupg.GPG()
dcr_key = gpg.list_keys(keys=DCR_KEY_LONG_FP)
if dcr_key:
    print("Decred developers' public key", DCR_KEY, "has already been imported.")
else:
    print("Importing Decred developers' key", DCR_KEY, "from", DCR_KEYHOST)
    gpg_out = gpg.recv_keys(DCR_KEYHOST, DCR_KEY_LONG_FP)
    if not gpg_out.results[0]['ok']:
        print("[ FAIL ] GPG failed to import key", DCR_KEY, "from", DCR_KEYHOST + ". It may have timed out.")
        print("Make sure you can send traffic using port TCP 11371.")
        exit(2)

response = urllib.request.urlopen(DCR_GITHUB_TAG + "/v" + VERSION, context=client_context)
if response.code == 404:
    print("ERROR: could not find release version", VERSION, "on Github.")
    exit(8)

if not Path(MANIFEST).is_file() and not Path(MANIFEST_ASC).is_file() and not Path(DECRED_PKG).is_file():
    print("Downloading files from Github...")
    if not Path(MANIFEST).is_file():
        print("-", MANIFEST)
        with urllib.request.urlopen(DCR_GITHUB + "/v" + VERSION + "/" + MANIFEST, context=client_context) \
                as response, open(MANIFEST, 'wb') as out_file:
            shutil.copyfileobj(response, out_file)
    if not Path(MANIFEST_ASC).is_file():
        print("-", MANIFEST_ASC)
        with urllib.request.urlopen(DCR_GITHUB + "/v" + VERSION + "/" + MANIFEST_ASC, context=client_context) \
                as response, open(MANIFEST_ASC, 'wb') as out_file:
            shutil.copyfileobj(response, out_file)
    if not Path(DECRED_PKG).is_file():
        print("-", DECRED_PKG)
        with urllib.request.urlopen(DCR_GITHUB + "/v" + VERSION + "/" + DECRED_PKG, context=client_context) \
                as response, open(DECRED_PKG, 'wb') as out_file:
            shutil.copyfileobj(response, out_file)
else:
    print("All files have already been copied locally.")
    print("- ", MANIFEST)
    print("- ", MANIFEST_ASC)
    print("- ", DECRED_PKG)


print("\nVerifying digital signature and hash...\n")
v = open(MANIFEST_ASC, "rb")
verify_result = gpg.verify_file(v, MANIFEST)
v.close()

if verify_result.status == "signature valid":
    hasher = hashlib.sha256()
    with open(DECRED_PKG, '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(MANIFEST, "r")
    for line in file:
        if re.search(DIGEST, line):
            hash_found = True
    file.close()
    if hash_found:
        print("[ SUCCESS ] GPG signature for", MANIFEST, "successfully verified.")
        print("[ SUCCESS ] SHA-256 hash of", DECRED_PKG, "successfully verified.\n")
        # The script won't delete files.
        print("It's now safe to delete files", MANIFEST, "and", MANIFEST_ASC, "\n")
        print("RESULT: SUCCESS. It means that", DECRED_PKG, "wasn't modified after its creation")
        print("and that the file that contains the proof was signed by Decred developers' private key.")
        print("Learn more at https://stakey.club/en/verifying-digital-signatures/\n")
        # The script won't install or run the wallet.
        if platform.system() == 'Darwin' and MODE == 0:
            print("To install, open", DECRED_PKG, "and move Decrediton.app to the Applications folder.")
        elif platform.system() == 'Windows' and MODE == 0:
            print("To install, run", DECRED_PKG)
        else:
            print("To install, unpack", DECRED_PKG + ", enter the directory and run the executable.")

    else:
        print("[ SUCCESS ] GPG signature for", DECRED_PKG, "successfully verified.")
        print("[ FAIL ] SHA-256 hash of", DECRED_PKG, "verification FAILED!\n")
        print("RESULT: FAIL. It means that the manifest wasn't modified after its creation")
        print("but", DECRED_PKG, "is incomplete or was probably modified after its creation.")
        exit(3)

else:
    print("[ FAIL ] GPG did not find a \"good signature\" for", MANIFEST + ". Verification FAILED!")
    exit(4)