201 lines
5.3 KiB
Go
201 lines
5.3 KiB
Go
//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
|
||
|
|
}
|