Files
goget/cmd/goget/verify.go
T

201 lines
5.3 KiB
Go
Raw Normal View History

//go:build linux || freebsd
// +build linux freebsd
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"codeberg.org/petrbalvin/goget/internal/crypto"
)
// parseChecksumFile reads a checksum file (SHA256SUMS format) and finds
// the expected checksum for the given filename.
func parseChecksumFile(checksumFile, outputFile string) (string, error) {
data, err := os.ReadFile(checksumFile)
if err != nil {
return "", fmt.Errorf("failed to read checksum file: %w", err)
}
targetName := filepath.Base(outputFile)
lines := strings.Split(string(data), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
// Format: <checksum> <filename> or <checksum> *<filename>
parts := strings.Fields(line)
if len(parts) < 2 {
continue
}
checksum := parts[0]
filename := strings.TrimPrefix(parts[1], "*")
if filename == targetName {
return checksum, nil
}
// Also check if the output file matches
if filename == outputFile {
return checksum, nil
}
}
return "", fmt.Errorf("no checksum found for %s in %s", targetName, checksumFile)
}
// verifyChecksum verifies the checksum of a downloaded file.
// Returns true if the checksum matches, false otherwise.
// Returns an error if verification could not be performed.
func verifyChecksum(outputFile, checksumFlag, checksumAlgo string, verbose bool) (bool, error) {
if verbose {
fmt.Fprintf(os.Stderr, "\nVerifying checksum...\n")
}
algo := crypto.SHA256
switch strings.ToLower(checksumAlgo) {
case "sha256":
algo = crypto.SHA256
case "sha512":
algo = crypto.SHA512
case "blake2b":
algo = crypto.BLAKE2b
case "sha3-256", "sha3_256":
algo = crypto.SHA3_256
case "sha3-512", "sha3_512":
algo = crypto.SHA3_512
case "md5":
algo = crypto.MD5
default:
return false, fmt.Errorf("unsupported checksum algorithm: %s", checksumAlgo)
}
verifier, verr := crypto.NewChecksumVerifier(algo, checksumFlag)
if verr != nil {
return false, verr
}
match, actual, verr := verifier.VerifyFile(outputFile)
if verr != nil {
return false, verr
}
if !match {
return false, fmt.Errorf("checksum mismatch\n Expected: %s\n Actual: %s", checksumFlag, actual)
}
if verbose {
fmt.Fprintf(os.Stderr, "Checksum OK: %s\n", actual)
}
return true, nil
}
// verifyPGP handles PGP signature verification and PGP decryption.
// Returns true on success, false on failure, and any error encountered.
func verifyPGP(outputFile string, pgpVerify, pgpDecrypt bool, pgpKeyFlag, pgpPassphraseFlag, pgpSignatureFlag *string, verbose bool) (bool, error) {
// PGP signature verification
if pgpVerify && outputFile != "" {
if verbose {
fmt.Fprintf(os.Stderr, "\nVerifying PGP signature...\n")
}
pgpCfg := &crypto.PGPConfig{
PublicKeyPath: *pgpKeyFlag,
Passphrase: *pgpPassphraseFlag,
}
verifier := crypto.NewPGPVerifier(pgpCfg)
var valid bool
var identity string
var err error
if *pgpSignatureFlag != "" {
// Detached signature
valid, identity, err = verifier.VerifyFile(outputFile, *pgpSignatureFlag)
} else {
// Try to find signature file automatically
sigFiles := []string{
outputFile + ".asc",
outputFile + ".sig",
outputFile + ".gpg",
}
for _, sigFile := range sigFiles {
if _, statErr := os.Stat(sigFile); statErr == nil {
valid, identity, err = verifier.VerifyFile(outputFile, sigFile)
if valid {
break
}
}
}
if !valid {
err = fmt.Errorf("signature file not found or invalid (tried: %s)", strings.Join(sigFiles, ", "))
}
}
if err != nil {
return false, fmt.Errorf("pgp verification failed: %w", err)
}
if !valid {
return false, fmt.Errorf("pgp signature verification failed")
}
if verbose {
fmt.Fprintf(os.Stderr, "PGP signature verified OK (signed by: %s)\n", identity)
}
}
// PGP decryption
if pgpDecrypt && outputFile != "" {
if verbose {
fmt.Fprintf(os.Stderr, "\nDecrypting PGP file...\n")
}
// Read file to check if it's PGP encrypted
data, err := os.ReadFile(outputFile)
if err != nil {
return false, fmt.Errorf("failed to read file for pgp check: %w", err)
}
if !crypto.IsPGPEncrypted(data) {
if verbose {
fmt.Fprintf(os.Stderr, "File is not PGP encrypted, skipping decryption\n")
}
return true, nil
}
pgpCfg := &crypto.PGPConfig{
PrivateKeyPath: *pgpKeyFlag,
Passphrase: *pgpPassphraseFlag,
}
decryptor := crypto.NewPGPDecryptor(pgpCfg)
// Determine output path
decryptedPath := outputFile
if strings.HasSuffix(outputFile, ".gpg") {
decryptedPath = strings.TrimSuffix(outputFile, ".gpg")
} else if strings.HasSuffix(outputFile, ".pgp") {
decryptedPath = strings.TrimSuffix(outputFile, ".pgp")
} else if strings.HasSuffix(outputFile, ".asc") {
decryptedPath = strings.TrimSuffix(outputFile, ".asc")
} else {
decryptedPath = outputFile + ".decrypted"
}
if err := decryptor.DecryptFile(outputFile, decryptedPath); err != nil {
return false, fmt.Errorf("pgp decryption failed: %w", err)
}
if verbose {
fmt.Fprintf(os.Stderr, "PGP decryption successful: %s\n", decryptedPath)
}
// Remove encrypted file
if err := os.Remove(outputFile); err != nil {
if verbose {
fmt.Fprintf(os.Stderr, "Warning: failed to remove encrypted file: %v\n", err)
}
}
}
return true, nil
}